mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
Change prettier rules
This commit is contained in:
@@ -3,14 +3,14 @@ import { Helmet } from 'react-helmet-async';
|
||||
import { FormattedMessage as Message, MessageDescriptor } from 'react-intl';
|
||||
|
||||
export default function AuthTitle({ title }: { title: MessageDescriptor }) {
|
||||
return (
|
||||
<Message {...title}>
|
||||
{(msg) => (
|
||||
<span>
|
||||
{msg}
|
||||
<Helmet title={msg as string} />
|
||||
</span>
|
||||
)}
|
||||
</Message>
|
||||
);
|
||||
return (
|
||||
<Message {...title}>
|
||||
{(msg) => (
|
||||
<span>
|
||||
{msg}
|
||||
<Helmet title={msg as string} />
|
||||
</span>
|
||||
)}
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,63 +11,63 @@ import Context, { AuthContext } from './Context';
|
||||
*/
|
||||
|
||||
class BaseAuthBody extends React.Component<
|
||||
// TODO: this may be converted to generic type RouteComponentProps<T>
|
||||
RouteComponentProps<Record<string, any>>
|
||||
// TODO: this may be converted to generic type RouteComponentProps<T>
|
||||
RouteComponentProps<Record<string, any>>
|
||||
> {
|
||||
static contextType = Context;
|
||||
/* TODO: use declare */ context: React.ContextType<typeof Context>;
|
||||
prevErrors: AuthContext['auth']['error'];
|
||||
static contextType = Context;
|
||||
/* TODO: use declare */ context: React.ContextType<typeof Context>;
|
||||
prevErrors: AuthContext['auth']['error'];
|
||||
|
||||
autoFocusField: string | null = '';
|
||||
autoFocusField: string | null = '';
|
||||
|
||||
componentDidMount() {
|
||||
this.prevErrors = this.context.auth.error;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.context.auth.error !== this.prevErrors) {
|
||||
this.form.setErrors(this.context.auth.error || {});
|
||||
this.context.requestRedraw();
|
||||
componentDidMount() {
|
||||
this.prevErrors = this.context.auth.error;
|
||||
}
|
||||
|
||||
this.prevErrors = this.context.auth.error;
|
||||
}
|
||||
componentDidUpdate() {
|
||||
if (this.context.auth.error !== this.prevErrors) {
|
||||
this.form.setErrors(this.context.auth.error || {});
|
||||
this.context.requestRedraw();
|
||||
}
|
||||
|
||||
renderErrors(): ReactNode {
|
||||
const error = this.form.getFirstError();
|
||||
|
||||
if (error === null) {
|
||||
return null;
|
||||
this.prevErrors = this.context.auth.error;
|
||||
}
|
||||
|
||||
return <AuthError error={error} onClose={this.onClearErrors} />;
|
||||
}
|
||||
renderErrors(): ReactNode {
|
||||
const error = this.form.getFirstError();
|
||||
|
||||
onFormSubmit() {
|
||||
this.context.resolve(this.serialize());
|
||||
}
|
||||
if (error === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
onClearErrors = () => this.context.clearErrors();
|
||||
|
||||
form = new FormModel({
|
||||
renderErrors: false,
|
||||
});
|
||||
|
||||
bindField(name: string) {
|
||||
return this.form.bindField(name);
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.form.serialize();
|
||||
}
|
||||
|
||||
autoFocus() {
|
||||
const fieldId = this.autoFocusField;
|
||||
|
||||
if (fieldId && this.form.hasField(fieldId)) {
|
||||
this.form.focus(fieldId);
|
||||
return <AuthError error={error} onClose={this.onClearErrors} />;
|
||||
}
|
||||
|
||||
onFormSubmit() {
|
||||
this.context.resolve(this.serialize());
|
||||
}
|
||||
|
||||
onClearErrors = () => this.context.clearErrors();
|
||||
|
||||
form = new FormModel({
|
||||
renderErrors: false,
|
||||
});
|
||||
|
||||
bindField(name: string) {
|
||||
return this.form.bindField(name);
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.form.serialize();
|
||||
}
|
||||
|
||||
autoFocus() {
|
||||
const fieldId = this.autoFocusField;
|
||||
|
||||
if (fieldId && this.form.hasField(fieldId)) {
|
||||
this.form.focus(fieldId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseAuthBody;
|
||||
|
||||
@@ -4,28 +4,28 @@ import { User } from 'app/components/user';
|
||||
import { State as AuthState } from './reducer';
|
||||
|
||||
export interface AuthContext {
|
||||
auth: AuthState;
|
||||
user: User;
|
||||
requestRedraw: () => Promise<void>;
|
||||
clearErrors: () => void;
|
||||
resolve: (payload: { [key: string]: any } | undefined) => void;
|
||||
reject: (payload: { [key: string]: any } | undefined) => void;
|
||||
auth: AuthState;
|
||||
user: User;
|
||||
requestRedraw: () => Promise<void>;
|
||||
clearErrors: () => void;
|
||||
resolve: (payload: { [key: string]: any } | undefined) => void;
|
||||
reject: (payload: { [key: string]: any } | undefined) => void;
|
||||
}
|
||||
|
||||
const Context = React.createContext<AuthContext>({
|
||||
auth: {
|
||||
error: null,
|
||||
login: '',
|
||||
scopes: [],
|
||||
} as any,
|
||||
user: {
|
||||
id: null,
|
||||
isGuest: true,
|
||||
} as any,
|
||||
async requestRedraw() {},
|
||||
clearErrors() {},
|
||||
resolve() {},
|
||||
reject() {},
|
||||
auth: {
|
||||
error: null,
|
||||
login: '',
|
||||
scopes: [],
|
||||
} as any,
|
||||
user: {
|
||||
id: null,
|
||||
isGuest: true,
|
||||
} as any,
|
||||
async requestRedraw() {},
|
||||
clearErrors() {},
|
||||
resolve() {},
|
||||
reject() {},
|
||||
});
|
||||
Context.displayName = 'AuthContext';
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,16 +2,13 @@
|
||||
|
||||
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`
|
||||
- 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`
|
||||
- whatever else you need
|
||||
- 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`
|
||||
- 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`
|
||||
- whatever else you need
|
||||
|
||||
Commit id with example implementation: f4d315c
|
||||
|
||||
@@ -4,36 +4,32 @@ import { FormattedMessage as Message, MessageDescriptor } from 'react-intl';
|
||||
import Context, { AuthContext } from './Context';
|
||||
|
||||
interface Props {
|
||||
isAvailable?: (context: AuthContext) => boolean;
|
||||
payload?: Record<string, any>;
|
||||
label: MessageDescriptor;
|
||||
isAvailable?: (context: AuthContext) => boolean;
|
||||
payload?: Record<string, any>;
|
||||
label: MessageDescriptor;
|
||||
}
|
||||
|
||||
const RejectionLink: ComponentType<Props> = ({
|
||||
isAvailable,
|
||||
payload,
|
||||
label,
|
||||
}) => {
|
||||
const context = useContext(Context);
|
||||
const RejectionLink: ComponentType<Props> = ({ isAvailable, payload, label }) => {
|
||||
const context = useContext(Context);
|
||||
|
||||
if (isAvailable && !isAvailable(context)) {
|
||||
// TODO: if want to properly support multiple links, we should control
|
||||
// the dividers ' | ' rendered from factory too
|
||||
return null;
|
||||
}
|
||||
if (isAvailable && !isAvailable(context)) {
|
||||
// TODO: if want to properly support multiple links, we should control
|
||||
// the dividers ' | ' rendered from factory too
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
context.reject(payload);
|
||||
}}
|
||||
>
|
||||
<Message {...label} />
|
||||
</a>
|
||||
);
|
||||
context.reject(payload);
|
||||
}}
|
||||
>
|
||||
<Message {...label} />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default RejectionLink;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"title": "User Agreement",
|
||||
"accept": "Accept",
|
||||
"declineAndLogout": "Decline and logout",
|
||||
"description1": "We have updated our {link}.",
|
||||
"termsOfService": "terms of service",
|
||||
"description2": "In order to continue using {name} service, you need to accept them."
|
||||
"title": "User Agreement",
|
||||
"accept": "Accept",
|
||||
"declineAndLogout": "Decline and logout",
|
||||
"description1": "We have updated our {link}.",
|
||||
"termsOfService": "terms of service",
|
||||
"description2": "In order to continue using {name} service, you need to accept them."
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import Body from './AcceptRulesBody';
|
||||
import messages from './AcceptRules.intl.json';
|
||||
|
||||
export default factory({
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'darkBlue',
|
||||
autoFocus: true,
|
||||
label: messages.accept,
|
||||
},
|
||||
links: {
|
||||
label: messages.declineAndLogout,
|
||||
},
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'darkBlue',
|
||||
autoFocus: true,
|
||||
label: messages.accept,
|
||||
},
|
||||
links: {
|
||||
label: messages.declineAndLogout,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,38 +11,38 @@ import styles from './acceptRules.scss';
|
||||
import messages from './AcceptRules.intl.json';
|
||||
|
||||
export default class AcceptRulesBody extends BaseAuthBody {
|
||||
static displayName = 'AcceptRulesBody';
|
||||
static panelId = 'acceptRules';
|
||||
static displayName = 'AcceptRulesBody';
|
||||
static panelId = 'acceptRules';
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<div className={styles.security}>
|
||||
<span className={icons.lock} />
|
||||
</div>
|
||||
<div className={styles.security}>
|
||||
<span className={icons.lock} />
|
||||
</div>
|
||||
|
||||
<p className={styles.descriptionText}>
|
||||
<Message
|
||||
{...messages.description1}
|
||||
values={{
|
||||
link: (
|
||||
<Link to="/rules" target="_blank">
|
||||
<Message {...messages.termsOfService} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<Message
|
||||
{...messages.description2}
|
||||
values={{
|
||||
name: <Message {...appInfo.appName} />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<p className={styles.descriptionText}>
|
||||
<Message
|
||||
{...messages.description1}
|
||||
values={{
|
||||
link: (
|
||||
<Link to="/rules" target="_blank">
|
||||
<Message {...messages.termsOfService} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<Message
|
||||
{...messages.description2}
|
||||
values={{
|
||||
name: <Message {...appInfo.appName} />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 8px;
|
||||
color: #aaa;
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 8px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
// TODO: вынести иконки такого типа в какую-то внешнюю структуру?
|
||||
.security {
|
||||
color: #fff;
|
||||
font-size: 90px;
|
||||
line-height: 1;
|
||||
margin-bottom: 15px;
|
||||
color: #fff;
|
||||
font-size: 90px;
|
||||
line-height: 1;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@@ -5,200 +5,190 @@ import expect from 'app/test/unexpected';
|
||||
import request from 'app/services/request';
|
||||
|
||||
import {
|
||||
setLoadingState,
|
||||
oAuthValidate,
|
||||
oAuthComplete,
|
||||
setClient,
|
||||
setOAuthRequest,
|
||||
setScopes,
|
||||
setOAuthCode,
|
||||
requirePermissionsAccept,
|
||||
login,
|
||||
setLogin,
|
||||
setLoadingState,
|
||||
oAuthValidate,
|
||||
oAuthComplete,
|
||||
setClient,
|
||||
setOAuthRequest,
|
||||
setScopes,
|
||||
setOAuthCode,
|
||||
requirePermissionsAccept,
|
||||
login,
|
||||
setLogin,
|
||||
} from 'app/components/auth/actions';
|
||||
import { OauthData, OAuthValidateResponse } from '../../services/api/oauth';
|
||||
|
||||
const oauthData: OauthData = {
|
||||
clientId: '',
|
||||
redirectUrl: '',
|
||||
responseType: '',
|
||||
scope: '',
|
||||
state: '',
|
||||
prompt: 'none',
|
||||
clientId: '',
|
||||
redirectUrl: '',
|
||||
responseType: '',
|
||||
scope: '',
|
||||
state: '',
|
||||
prompt: 'none',
|
||||
};
|
||||
|
||||
describe('components/auth/actions', () => {
|
||||
const dispatch = sinon.stub().named('store.dispatch');
|
||||
const getState = sinon.stub().named('store.getState');
|
||||
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> {
|
||||
const thunk = fn(...args);
|
||||
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);
|
||||
}
|
||||
return thunk(dispatch, getState);
|
||||
}
|
||||
|
||||
function expectDispatchCalls(calls: Array<Array<ReduxAction>>) {
|
||||
expect(dispatch, 'to have calls satisfying', [
|
||||
[setLoadingState(true)],
|
||||
...calls,
|
||||
[setLoadingState(false)],
|
||||
]);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
dispatch.reset();
|
||||
getState.reset();
|
||||
getState.returns({});
|
||||
sinon.stub(request, 'get').named('request.get');
|
||||
sinon.stub(request, 'post').named('request.post');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(request.get as any).restore();
|
||||
(request.post as any).restore();
|
||||
});
|
||||
|
||||
describe('#oAuthValidate()', () => {
|
||||
let resp: OAuthValidateResponse;
|
||||
function expectDispatchCalls(calls: Array<Array<ReduxAction>>) {
|
||||
expect(dispatch, 'to have calls satisfying', [[setLoadingState(true)], ...calls, [setLoadingState(false)]]);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resp = {
|
||||
client: {
|
||||
id: '123',
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
oAuth: {
|
||||
state: 123,
|
||||
},
|
||||
session: {
|
||||
scopes: ['account_info'],
|
||||
},
|
||||
};
|
||||
|
||||
(request.get as any).returns(Promise.resolve(resp));
|
||||
dispatch.reset();
|
||||
getState.reset();
|
||||
getState.returns({});
|
||||
sinon.stub(request, 'get').named('request.get');
|
||||
sinon.stub(request, 'post').named('request.post');
|
||||
});
|
||||
|
||||
it('should send get request to an api', () =>
|
||||
callThunk(oAuthValidate, oauthData).then(() => {
|
||||
expect(request.get, 'to have a call satisfying', [
|
||||
'/api/oauth2/v1/validate',
|
||||
{},
|
||||
]);
|
||||
}));
|
||||
|
||||
it('should dispatch setClient, setOAuthRequest and setScopes', () =>
|
||||
callThunk(oAuthValidate, oauthData).then(() => {
|
||||
expectDispatchCalls([
|
||||
[setClient(resp.client)],
|
||||
[
|
||||
setOAuthRequest({
|
||||
...resp.oAuth,
|
||||
prompt: 'none',
|
||||
loginHint: undefined,
|
||||
}),
|
||||
],
|
||||
[setScopes(resp.session.scopes)],
|
||||
]);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('#oAuthComplete()', () => {
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
auth: {
|
||||
oauth: oauthData,
|
||||
},
|
||||
});
|
||||
afterEach(() => {
|
||||
(request.get as any).restore();
|
||||
(request.post as any).restore();
|
||||
});
|
||||
|
||||
it('should post to api/oauth2/complete', () => {
|
||||
(request.post as any).returns(
|
||||
Promise.resolve({
|
||||
redirectUri: '',
|
||||
}),
|
||||
);
|
||||
describe('#oAuthValidate()', () => {
|
||||
let resp: OAuthValidateResponse;
|
||||
|
||||
return callThunk(oAuthComplete).then(() => {
|
||||
expect(request.post, 'to have a call satisfying', [
|
||||
'/api/oauth2/v1/complete?client_id=&redirect_uri=&response_type=&description=&scope=&prompt=none&login_hint=&state=',
|
||||
{},
|
||||
]);
|
||||
});
|
||||
beforeEach(() => {
|
||||
resp = {
|
||||
client: {
|
||||
id: '123',
|
||||
name: '',
|
||||
description: '',
|
||||
},
|
||||
oAuth: {
|
||||
state: 123,
|
||||
},
|
||||
session: {
|
||||
scopes: ['account_info'],
|
||||
},
|
||||
};
|
||||
|
||||
(request.get as any).returns(Promise.resolve(resp));
|
||||
});
|
||||
|
||||
it('should send get request to an api', () =>
|
||||
callThunk(oAuthValidate, oauthData).then(() => {
|
||||
expect(request.get, 'to have a call satisfying', ['/api/oauth2/v1/validate', {}]);
|
||||
}));
|
||||
|
||||
it('should dispatch setClient, setOAuthRequest and setScopes', () =>
|
||||
callThunk(oAuthValidate, oauthData).then(() => {
|
||||
expectDispatchCalls([
|
||||
[setClient(resp.client)],
|
||||
[
|
||||
setOAuthRequest({
|
||||
...resp.oAuth,
|
||||
prompt: 'none',
|
||||
loginHint: undefined,
|
||||
}),
|
||||
],
|
||||
[setScopes(resp.session.scopes)],
|
||||
]);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should dispatch setOAuthCode for static_page redirect', () => {
|
||||
const resp = {
|
||||
success: true,
|
||||
redirectUri: 'static_page?code=123&state=',
|
||||
};
|
||||
describe('#oAuthComplete()', () => {
|
||||
beforeEach(() => {
|
||||
getState.returns({
|
||||
auth: {
|
||||
oauth: oauthData,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
(request.post as any).returns(Promise.resolve(resp));
|
||||
it('should post to api/oauth2/complete', () => {
|
||||
(request.post as any).returns(
|
||||
Promise.resolve({
|
||||
redirectUri: '',
|
||||
}),
|
||||
);
|
||||
|
||||
return callThunk(oAuthComplete).then(() => {
|
||||
expectDispatchCalls([
|
||||
[
|
||||
setOAuthCode({
|
||||
success: true,
|
||||
code: '123',
|
||||
displayCode: false,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
});
|
||||
return callThunk(oAuthComplete).then(() => {
|
||||
expect(request.post, 'to have a call satisfying', [
|
||||
'/api/oauth2/v1/complete?client_id=&redirect_uri=&response_type=&description=&scope=&prompt=none&login_hint=&state=',
|
||||
{},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch setOAuthCode for static_page redirect', () => {
|
||||
const resp = {
|
||||
success: true,
|
||||
redirectUri: 'static_page?code=123&state=',
|
||||
};
|
||||
|
||||
(request.post as any).returns(Promise.resolve(resp));
|
||||
|
||||
return callThunk(oAuthComplete).then(() => {
|
||||
expectDispatchCalls([
|
||||
[
|
||||
setOAuthCode({
|
||||
success: true,
|
||||
code: '123',
|
||||
displayCode: false,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve to with success false and redirectUri for access_denied', async () => {
|
||||
const resp = {
|
||||
statusCode: 401,
|
||||
error: 'access_denied',
|
||||
redirectUri: 'redirectUri',
|
||||
};
|
||||
|
||||
(request.post as any).returns(Promise.reject(resp));
|
||||
|
||||
const data = await callThunk(oAuthComplete);
|
||||
|
||||
expect(data, 'to equal', {
|
||||
success: false,
|
||||
redirectUri: 'redirectUri',
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch requirePermissionsAccept if accept_required', () => {
|
||||
const resp = {
|
||||
statusCode: 401,
|
||||
error: 'accept_required',
|
||||
};
|
||||
|
||||
(request.post as any).returns(Promise.reject(resp));
|
||||
|
||||
return callThunk(oAuthComplete).catch((error) => {
|
||||
expect(error.acceptRequired, 'to be true');
|
||||
expectDispatchCalls([[requirePermissionsAccept()]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve to with success false and redirectUri for access_denied', async () => {
|
||||
const resp = {
|
||||
statusCode: 401,
|
||||
error: 'access_denied',
|
||||
redirectUri: 'redirectUri',
|
||||
};
|
||||
describe('#login()', () => {
|
||||
describe('when correct login was entered', () => {
|
||||
beforeEach(() => {
|
||||
(request.post as any).returns(
|
||||
Promise.reject({
|
||||
errors: {
|
||||
password: 'error.password_required',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
(request.post as any).returns(Promise.reject(resp));
|
||||
|
||||
const data = await callThunk(oAuthComplete);
|
||||
|
||||
expect(data, 'to equal', {
|
||||
success: false,
|
||||
redirectUri: 'redirectUri',
|
||||
});
|
||||
it('should set login', () =>
|
||||
callThunk(login, { login: 'foo' }).then(() => {
|
||||
expectDispatchCalls([[setLogin('foo')]]);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch requirePermissionsAccept if accept_required', () => {
|
||||
const resp = {
|
||||
statusCode: 401,
|
||||
error: 'accept_required',
|
||||
};
|
||||
|
||||
(request.post as any).returns(Promise.reject(resp));
|
||||
|
||||
return callThunk(oAuthComplete).catch((error) => {
|
||||
expect(error.acceptRequired, 'to be true');
|
||||
expectDispatchCalls([[requirePermissionsAccept()]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#login()', () => {
|
||||
describe('when correct login was entered', () => {
|
||||
beforeEach(() => {
|
||||
(request.post as any).returns(
|
||||
Promise.reject({
|
||||
errors: {
|
||||
password: 'error.password_required',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set login', () =>
|
||||
callThunk(login, { login: 'foo' }).then(() => {
|
||||
expectDispatchCalls([[setLogin('foo')]]);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"accountActivationTitle": "Account activation",
|
||||
"activationMailWasSent": "Please check {email} for the message with further instructions",
|
||||
"activationMailWasSentNoEmail": "Please check your E‑mail for the message with further instructions",
|
||||
"confirmEmail": "Confirm E‑mail",
|
||||
"didNotReceivedEmail": "Did not received E‑mail?",
|
||||
"enterTheCode": "Enter the code from E‑mail here"
|
||||
"accountActivationTitle": "Account activation",
|
||||
"activationMailWasSent": "Please check {email} for the message with further instructions",
|
||||
"activationMailWasSentNoEmail": "Please check your E‑mail for the message with further instructions",
|
||||
"confirmEmail": "Confirm E‑mail",
|
||||
"didNotReceivedEmail": "Did not received E‑mail?",
|
||||
"enterTheCode": "Enter the code from E‑mail here"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import messages from './Activation.intl.json';
|
||||
import Body from './ActivationBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.accountActivationTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'blue',
|
||||
label: messages.confirmEmail,
|
||||
},
|
||||
links: {
|
||||
label: messages.didNotReceivedEmail,
|
||||
},
|
||||
title: messages.accountActivationTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'blue',
|
||||
label: messages.confirmEmail,
|
||||
},
|
||||
links: {
|
||||
label: messages.didNotReceivedEmail,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,49 +9,48 @@ import styles from './activation.scss';
|
||||
import messages from './Activation.intl.json';
|
||||
|
||||
export default class ActivationBody extends BaseAuthBody {
|
||||
static displayName = 'ActivationBody';
|
||||
static panelId = 'activation';
|
||||
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;
|
||||
const { email } = this.context.user;
|
||||
render() {
|
||||
const { key } = this.props.match.params;
|
||||
const { email } = this.context.user;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<div className={styles.description}>
|
||||
<div className={styles.descriptionImage} />
|
||||
<div className={styles.description}>
|
||||
<div className={styles.descriptionImage} />
|
||||
|
||||
<div className={styles.descriptionText}>
|
||||
{email ? (
|
||||
<Message
|
||||
{...messages.activationMailWasSent}
|
||||
values={{
|
||||
email: <b>{email}</b>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Message {...messages.activationMailWasSentNoEmail} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...this.bindField('key')}
|
||||
color="blue"
|
||||
center
|
||||
required
|
||||
value={key}
|
||||
readOnly={!!key}
|
||||
autoComplete="off"
|
||||
placeholder={messages.enterTheCode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className={styles.descriptionText}>
|
||||
{email ? (
|
||||
<Message
|
||||
{...messages.activationMailWasSent}
|
||||
values={{
|
||||
email: <b>{email}</b>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Message {...messages.activationMailWasSentNoEmail} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...this.bindField('key')}
|
||||
color="blue"
|
||||
center
|
||||
required
|
||||
value={key}
|
||||
readOnly={!!key}
|
||||
autoComplete="off"
|
||||
placeholder={messages.enterTheCode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
}
|
||||
|
||||
.descriptionImage {
|
||||
composes: envelope from '~app/components/ui/icons.scss';
|
||||
composes: envelope from '~app/components/ui/icons.scss';
|
||||
|
||||
font-size: 100px;
|
||||
color: $blue;
|
||||
font-size: 100px;
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
.descriptionText {
|
||||
font-family: $font-family-title;
|
||||
margin: 5px 0 19px;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
font-family: $font-family-title;
|
||||
margin: 5px 0 19px;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"appName": "Ely Accounts",
|
||||
"goToAuth": "Go to auth",
|
||||
"appDescription": "You are on the Ely.by authorization service, that allows you to safely perform any operations on your account. This single entry point for websites and desktop software, including game launchers.",
|
||||
"useItYourself": "Visit our {link}, to learn how to use this service in you projects.",
|
||||
"documentation": "documentation"
|
||||
"appName": "Ely Accounts",
|
||||
"goToAuth": "Go to auth",
|
||||
"appDescription": "You are on the Ely.by authorization service, that allows you to safely perform any operations on your account. This single entry point for websites and desktop software, including game launchers.",
|
||||
"useItYourself": "Visit our {link}, to learn how to use this service in you projects.",
|
||||
"documentation": "documentation"
|
||||
}
|
||||
|
||||
@@ -7,51 +7,49 @@ import styles from './appInfo.scss';
|
||||
import messages from './AppInfo.intl.json';
|
||||
|
||||
export default class AppInfo extends React.Component<{
|
||||
name?: string;
|
||||
description?: string;
|
||||
onGoToAuth: () => void;
|
||||
name?: string;
|
||||
description?: string;
|
||||
onGoToAuth: () => void;
|
||||
}> {
|
||||
render() {
|
||||
const { name, description, onGoToAuth } = this.props;
|
||||
render() {
|
||||
const { name, description, onGoToAuth } = this.props;
|
||||
|
||||
return (
|
||||
<div className={styles.appInfo}>
|
||||
<div className={styles.logoContainer}>
|
||||
<h2 className={styles.logo}>
|
||||
{name ? name : <Message {...messages.appName} />}
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.descriptionContainer}>
|
||||
{description ? (
|
||||
<p className={styles.description}>{description}</p>
|
||||
) : (
|
||||
<div>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.appDescription} />
|
||||
</p>
|
||||
<p className={styles.description}>
|
||||
<Message
|
||||
{...messages.useItYourself}
|
||||
values={{
|
||||
link: (
|
||||
<a href="http://docs.ely.by/oauth.html">
|
||||
<Message {...messages.documentation} />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
return (
|
||||
<div className={styles.appInfo}>
|
||||
<div className={styles.logoContainer}>
|
||||
<h2 className={styles.logo}>{name ? name : <Message {...messages.appName} />}</h2>
|
||||
</div>
|
||||
<div className={styles.descriptionContainer}>
|
||||
{description ? (
|
||||
<p className={styles.description}>{description}</p>
|
||||
) : (
|
||||
<div>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.appDescription} />
|
||||
</p>
|
||||
<p className={styles.description}>
|
||||
<Message
|
||||
{...messages.useItYourself}
|
||||
values={{
|
||||
link: (
|
||||
<a href="http://docs.ely.by/oauth.html">
|
||||
<Message {...messages.documentation} />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.goToAuth}>
|
||||
<Button onClick={onGoToAuth} label={messages.goToAuth} />
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<FooterMenu />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.goToAuth}>
|
||||
<Button onClick={onGoToAuth} label={messages.goToAuth} />
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<FooterMenu />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,71 +2,71 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
|
||||
.appInfo {
|
||||
max-width: 270px;
|
||||
margin: 0 auto;
|
||||
padding: 55px 25px;
|
||||
max-width: 270px;
|
||||
margin: 0 auto;
|
||||
padding: 55px 25px;
|
||||
}
|
||||
|
||||
.logoContainer {
|
||||
position: relative;
|
||||
padding: 15px 0;
|
||||
position: relative;
|
||||
padding: 15px 0;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
width: 40px;
|
||||
|
||||
background: $green;
|
||||
}
|
||||
background: $green;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: $font-family-title;
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
font-family: $font-family-title;
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.descriptionContainer {
|
||||
margin: 20px 0;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
$font-color: #ccc;
|
||||
font-family: $font-family-base;
|
||||
color: $font-color;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
margin-top: 7px;
|
||||
$font-color: #ccc;
|
||||
font-family: $font-family-base;
|
||||
color: $font-color;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
margin-top: 7px;
|
||||
|
||||
a {
|
||||
color: lighten($font-color, 10%);
|
||||
border-bottom-color: #666;
|
||||
a {
|
||||
color: lighten($font-color, 10%);
|
||||
border-bottom-color: #666;
|
||||
|
||||
&:hover {
|
||||
color: $font-color;
|
||||
&:hover {
|
||||
color: $font-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goToAuth {
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
.goToAuth {
|
||||
display: none;
|
||||
}
|
||||
.goToAuth {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.checkboxInput {
|
||||
margin-top: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
@@ -5,41 +5,36 @@ import { PanelBodyHeader } from 'app/components/ui/Panel';
|
||||
import { ValidationError } from 'app/components/ui/form/FormModel';
|
||||
|
||||
interface Props {
|
||||
error: ValidationError;
|
||||
onClose?: () => void;
|
||||
error: ValidationError;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
let autoHideTimer: number | null = null;
|
||||
function resetTimeout(): void {
|
||||
if (autoHideTimer) {
|
||||
clearTimeout(autoHideTimer);
|
||||
autoHideTimer = null;
|
||||
}
|
||||
if (autoHideTimer) {
|
||||
clearTimeout(autoHideTimer);
|
||||
autoHideTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
const AuthError: ComponentType<Props> = ({ error, onClose }) => {
|
||||
useEffect(() => {
|
||||
resetTimeout();
|
||||
useEffect(() => {
|
||||
resetTimeout();
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
return resetTimeout;
|
||||
}, [error, onClose]);
|
||||
return resetTimeout;
|
||||
}, [error, onClose]);
|
||||
|
||||
return (
|
||||
<PanelBodyHeader type="error" onClose={onClose}>
|
||||
{resolveError(error)}
|
||||
</PanelBodyHeader>
|
||||
);
|
||||
return (
|
||||
<PanelBodyHeader type="error" onClose={onClose}>
|
||||
{resolveError(error)}
|
||||
</PanelBodyHeader>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthError;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"chooseAccountTitle": "Choose an account",
|
||||
"addAccount": "Log into another account",
|
||||
"logoutAll": "Log out from all accounts",
|
||||
"pleaseChooseAccount": "Please select an account you're willing to use",
|
||||
"pleaseChooseAccountForApp": "Please select an account that you want to use to authorize {appName}"
|
||||
"chooseAccountTitle": "Choose an account",
|
||||
"addAccount": "Log into another account",
|
||||
"logoutAll": "Log out from all accounts",
|
||||
"pleaseChooseAccount": "Please select an account you're willing to use",
|
||||
"pleaseChooseAccountForApp": "Please select an account that you want to use to authorize {appName}"
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import messages from './ChooseAccount.intl.json';
|
||||
import Body from './ChooseAccountBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.chooseAccountTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
label: messages.addAccount,
|
||||
},
|
||||
links: [
|
||||
{
|
||||
label: messages.logoutAll,
|
||||
title: messages.chooseAccountTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
label: messages.addAccount,
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{
|
||||
label: messages.logoutAll,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -10,44 +10,44 @@ import styles from './chooseAccount.scss';
|
||||
import messages from './ChooseAccount.intl.json';
|
||||
|
||||
export default class ChooseAccountBody extends BaseAuthBody {
|
||||
static displayName = 'ChooseAccountBody';
|
||||
static panelId = 'chooseAccount';
|
||||
static displayName = 'ChooseAccountBody';
|
||||
static panelId = 'chooseAccount';
|
||||
|
||||
render() {
|
||||
const { client } = this.context.auth;
|
||||
render() {
|
||||
const { client } = this.context.auth;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<div className={styles.description}>
|
||||
{client ? (
|
||||
<Message
|
||||
{...messages.pleaseChooseAccountForApp}
|
||||
values={{
|
||||
appName: <span className={styles.appName}>{client.name}</span>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.pleaseChooseAccount} />
|
||||
<div className={styles.description}>
|
||||
{client ? (
|
||||
<Message
|
||||
{...messages.pleaseChooseAccountForApp}
|
||||
values={{
|
||||
appName: <span className={styles.appName}>{client.name}</span>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.pleaseChooseAccount} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.accountSwitcherContainer}>
|
||||
<AccountSwitcher
|
||||
allowAdd={false}
|
||||
allowLogout={false}
|
||||
highlightActiveAccount={false}
|
||||
onSwitch={this.onSwitch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
<div className={styles.accountSwitcherContainer}>
|
||||
<AccountSwitcher
|
||||
allowAdd={false}
|
||||
allowLogout={false}
|
||||
highlightActiveAccount={false}
|
||||
onSwitch={this.onSwitch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onSwitch = (account: Account): void => {
|
||||
this.context.resolve(account);
|
||||
};
|
||||
onSwitch = (account: Account): void => {
|
||||
this.context.resolve(account);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
|
||||
.accountSwitcherContainer {
|
||||
margin-left: -$bodyLeftRightPadding;
|
||||
margin-right: -$bodyLeftRightPadding;
|
||||
margin-left: -$bodyLeftRightPadding;
|
||||
margin-right: -$bodyLeftRightPadding;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family: $font-family-title;
|
||||
margin: 5px 0 19px;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
font-family: $font-family-title;
|
||||
margin: 5px 0 19px;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.appName {
|
||||
color: #fff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -7,44 +7,36 @@ import { Color } from 'app/components/ui';
|
||||
import BaseAuthBody from './BaseAuthBody';
|
||||
|
||||
export type Factory = () => {
|
||||
Title: ComponentType;
|
||||
Body: typeof BaseAuthBody;
|
||||
Footer: ComponentType;
|
||||
Links: ComponentType;
|
||||
Title: ComponentType;
|
||||
Body: typeof BaseAuthBody;
|
||||
Footer: ComponentType;
|
||||
Links: ComponentType;
|
||||
};
|
||||
|
||||
type RejectionLinkProps = ComponentProps<typeof RejectionLink>;
|
||||
interface FactoryParams {
|
||||
title: MessageDescriptor;
|
||||
body: typeof BaseAuthBody;
|
||||
footer: {
|
||||
color?: Color;
|
||||
label: string | MessageDescriptor;
|
||||
autoFocus?: boolean;
|
||||
};
|
||||
links?: RejectionLinkProps | Array<RejectionLinkProps>;
|
||||
title: MessageDescriptor;
|
||||
body: typeof BaseAuthBody;
|
||||
footer: {
|
||||
color?: Color;
|
||||
label: string | MessageDescriptor;
|
||||
autoFocus?: boolean;
|
||||
};
|
||||
links?: RejectionLinkProps | Array<RejectionLinkProps>;
|
||||
}
|
||||
|
||||
export default function ({
|
||||
title,
|
||||
body,
|
||||
footer,
|
||||
links,
|
||||
}: FactoryParams): Factory {
|
||||
return () => ({
|
||||
Title: () => <AuthTitle title={title} />,
|
||||
Body: body,
|
||||
Footer: () => <Button type="submit" {...footer} />,
|
||||
Links: () =>
|
||||
links ? (
|
||||
<span>
|
||||
{([] as Array<RejectionLinkProps>)
|
||||
.concat(links)
|
||||
.map((link, index) => [
|
||||
index ? ' | ' : '',
|
||||
<RejectionLink {...link} key={index} />,
|
||||
])}
|
||||
</span>
|
||||
) : null,
|
||||
});
|
||||
export default function ({ title, body, footer, links }: FactoryParams): Factory {
|
||||
return () => ({
|
||||
Title: () => <AuthTitle title={title} />,
|
||||
Body: body,
|
||||
Footer: () => <Button type="submit" {...footer} />,
|
||||
Links: () =>
|
||||
links ? (
|
||||
<span>
|
||||
{([] as Array<RejectionLinkProps>)
|
||||
.concat(links)
|
||||
.map((link, index) => [index ? ' | ' : '', <RejectionLink {...link} key={index} />])}
|
||||
</span>
|
||||
) : null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"authForAppSuccessful": "Authorization for {appName} was successfully completed",
|
||||
"authForAppFailed": "Authorization for {appName} was failed",
|
||||
"waitAppReaction": "Please, wait till your application response",
|
||||
"passCodeToApp": "To complete authorization process, please, provide the following code to {appName}",
|
||||
"copy": "Copy"
|
||||
"authForAppSuccessful": "Authorization for {appName} was successfully completed",
|
||||
"authForAppFailed": "Authorization for {appName} was failed",
|
||||
"waitAppReaction": "Please, wait till your application response",
|
||||
"passCodeToApp": "To complete authorization process, please, provide the following code to {appName}",
|
||||
"copy": "Copy"
|
||||
}
|
||||
|
||||
@@ -10,100 +10,95 @@ import messages from './Finish.intl.json';
|
||||
import styles from './finish.scss';
|
||||
|
||||
interface Props {
|
||||
appName: string;
|
||||
code?: string;
|
||||
state: string;
|
||||
displayCode?: boolean;
|
||||
success?: boolean;
|
||||
appName: string;
|
||||
code?: string;
|
||||
state: string;
|
||||
displayCode?: boolean;
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
class Finish extends React.Component<Props> {
|
||||
render() {
|
||||
const { appName, code, state, displayCode, success } = this.props;
|
||||
const authData = JSON.stringify({
|
||||
auth_code: code,
|
||||
state,
|
||||
});
|
||||
render() {
|
||||
const { appName, code, state, displayCode, success } = this.props;
|
||||
const authData = JSON.stringify({
|
||||
auth_code: code,
|
||||
state,
|
||||
});
|
||||
|
||||
history.pushState(null, document.title, `#${authData}`);
|
||||
history.pushState(null, document.title, `#${authData}`);
|
||||
|
||||
return (
|
||||
<div className={styles.finishPage}>
|
||||
<Helmet title={authData} />
|
||||
return (
|
||||
<div className={styles.finishPage}>
|
||||
<Helmet title={authData} />
|
||||
|
||||
{success ? (
|
||||
<div>
|
||||
<div className={styles.successBackground} />
|
||||
<div className={styles.greenTitle}>
|
||||
<Message
|
||||
{...messages.authForAppSuccessful}
|
||||
values={{
|
||||
appName: <span className={styles.appName}>{appName}</span>,
|
||||
}}
|
||||
/>
|
||||
{success ? (
|
||||
<div>
|
||||
<div className={styles.successBackground} />
|
||||
<div className={styles.greenTitle}>
|
||||
<Message
|
||||
{...messages.authForAppSuccessful}
|
||||
values={{
|
||||
appName: <span className={styles.appName}>{appName}</span>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{displayCode ? (
|
||||
<div data-testid="oauth-code-container">
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.passCodeToApp} values={{ appName }} />
|
||||
</div>
|
||||
<div className={styles.codeContainer}>
|
||||
<div className={styles.code}>{code}</div>
|
||||
</div>
|
||||
<Button color="green" small label={messages.copy} onClick={this.onCopyClick} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.waitAppReaction} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className={styles.failBackground} />
|
||||
<div className={styles.redTitle}>
|
||||
<Message
|
||||
{...messages.authForAppFailed}
|
||||
values={{
|
||||
appName: <span className={styles.appName}>{appName}</span>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.waitAppReaction} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{displayCode ? (
|
||||
<div data-testid="oauth-code-container">
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.passCodeToApp} values={{ appName }} />
|
||||
</div>
|
||||
<div className={styles.codeContainer}>
|
||||
<div className={styles.code}>{code}</div>
|
||||
</div>
|
||||
<Button
|
||||
color="green"
|
||||
small
|
||||
label={messages.copy}
|
||||
onClick={this.onCopyClick}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.waitAppReaction} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className={styles.failBackground} />
|
||||
<div className={styles.redTitle}>
|
||||
<Message
|
||||
{...messages.authForAppFailed}
|
||||
values={{
|
||||
appName: <span className={styles.appName}>{appName}</span>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.waitAppReaction} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onCopyClick: MouseEventHandler = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { code } = this.props;
|
||||
|
||||
if (code) {
|
||||
copy(code);
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
onCopyClick: MouseEventHandler = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { code } = this.props;
|
||||
|
||||
if (code) {
|
||||
copy(code);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(({ auth }: RootState) => {
|
||||
if (!auth || !auth.client || !auth.oauth) {
|
||||
throw new Error('Can not connect Finish component. No auth data in state');
|
||||
}
|
||||
if (!auth || !auth.client || !auth.oauth) {
|
||||
throw new Error('Can not connect Finish component. No auth data in state');
|
||||
}
|
||||
|
||||
return {
|
||||
appName: auth.client.name,
|
||||
code: auth.oauth.code,
|
||||
displayCode: auth.oauth.displayCode,
|
||||
state: auth.oauth.state,
|
||||
success: auth.oauth.success,
|
||||
};
|
||||
return {
|
||||
appName: auth.client.name,
|
||||
code: auth.oauth.code,
|
||||
displayCode: auth.oauth.displayCode,
|
||||
state: auth.oauth.state,
|
||||
success: auth.oauth.success,
|
||||
};
|
||||
})(Finish);
|
||||
|
||||
@@ -2,75 +2,75 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
|
||||
.finishPage {
|
||||
font-family: $font-family-title;
|
||||
position: relative;
|
||||
max-width: 515px;
|
||||
padding-top: 40px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
font-family: $font-family-title;
|
||||
position: relative;
|
||||
max-width: 515px;
|
||||
padding-top: 40px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.iconBackground {
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 200px;
|
||||
color: #e0d9cf;
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 200px;
|
||||
color: #e0d9cf;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.successBackground {
|
||||
composes: checkmark from '~app/components/ui/icons.scss';
|
||||
@extend .iconBackground;
|
||||
composes: checkmark from '~app/components/ui/icons.scss';
|
||||
@extend .iconBackground;
|
||||
}
|
||||
|
||||
.failBackground {
|
||||
composes: close from '~app/components/ui/icons.scss';
|
||||
@extend .iconBackground;
|
||||
composes: close from '~app/components/ui/icons.scss';
|
||||
@extend .iconBackground;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 22px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.greenTitle {
|
||||
composes: title;
|
||||
composes: title;
|
||||
|
||||
color: $green;
|
||||
color: $green;
|
||||
|
||||
.appName {
|
||||
color: darker($green);
|
||||
}
|
||||
.appName {
|
||||
color: darker($green);
|
||||
}
|
||||
}
|
||||
|
||||
.redTitle {
|
||||
composes: title;
|
||||
composes: title;
|
||||
|
||||
color: $red;
|
||||
color: $red;
|
||||
|
||||
.appName {
|
||||
color: darker($red);
|
||||
}
|
||||
.appName {
|
||||
color: darker($red);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.codeContainer {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.code {
|
||||
$border: 5px solid darker($green);
|
||||
$border: 5px solid darker($green);
|
||||
|
||||
display: inline-block;
|
||||
border-right: $border;
|
||||
border-left: $border;
|
||||
padding: 5px 10px;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
border-right: $border;
|
||||
border-left: $border;
|
||||
padding: 5px 10px;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"title": "Forgot password",
|
||||
"sendMail": "Send mail",
|
||||
"specifyEmail": "Specify the registration E‑mail address or last used username for your account and we will send an E‑mail with instructions for further password recovery.",
|
||||
"pleasePressButton": "Please press the button bellow to get an E‑mail with password recovery code.",
|
||||
"alreadyHaveCode": "Already have a code"
|
||||
"title": "Forgot password",
|
||||
"sendMail": "Send mail",
|
||||
"specifyEmail": "Specify the registration E‑mail address or last used username for your account and we will send an E‑mail with instructions for further password recovery.",
|
||||
"pleasePressButton": "Please press the button bellow to get an E‑mail with password recovery code.",
|
||||
"alreadyHaveCode": "Already have a code"
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import messages from './ForgotPassword.intl.json';
|
||||
import Body from './ForgotPasswordBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'lightViolet',
|
||||
autoFocus: true,
|
||||
label: messages.sendMail,
|
||||
},
|
||||
links: {
|
||||
label: messages.alreadyHaveCode,
|
||||
},
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'lightViolet',
|
||||
autoFocus: true,
|
||||
label: messages.sendMail,
|
||||
},
|
||||
links: {
|
||||
label: messages.alreadyHaveCode,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,87 +11,83 @@ import styles from './forgotPassword.scss';
|
||||
import messages from './ForgotPassword.intl.json';
|
||||
|
||||
export default class ForgotPasswordBody extends BaseAuthBody {
|
||||
static displayName = 'ForgotPasswordBody';
|
||||
static panelId = 'forgotPassword';
|
||||
static hasGoBack = true;
|
||||
static displayName = 'ForgotPasswordBody';
|
||||
static panelId = 'forgotPassword';
|
||||
static hasGoBack = true;
|
||||
|
||||
state = {
|
||||
isLoginEdit: false,
|
||||
};
|
||||
state = {
|
||||
isLoginEdit: false,
|
||||
};
|
||||
|
||||
autoFocusField = 'login';
|
||||
autoFocusField = 'login';
|
||||
|
||||
render() {
|
||||
const { isLoginEdit } = this.state;
|
||||
render() {
|
||||
const { isLoginEdit } = this.state;
|
||||
|
||||
const login = this.getLogin();
|
||||
const isLoginEditShown = isLoginEdit || !login;
|
||||
const login = this.getLogin();
|
||||
const isLoginEditShown = isLoginEdit || !login;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<PanelIcon icon="lock" />
|
||||
<PanelIcon icon="lock" />
|
||||
|
||||
{isLoginEditShown ? (
|
||||
<div>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.specifyEmail} />
|
||||
</p>
|
||||
<Input
|
||||
{...this.bindField('login')}
|
||||
icon="envelope"
|
||||
color="lightViolet"
|
||||
required
|
||||
placeholder={messages.accountEmail}
|
||||
defaultValue={login}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div data-testid="forgot-password-login">
|
||||
<div className={styles.login}>
|
||||
{login}
|
||||
<span
|
||||
className={styles.editLogin}
|
||||
onClick={this.onClickEdit}
|
||||
data-testid="edit-login"
|
||||
/>
|
||||
{isLoginEditShown ? (
|
||||
<div>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.specifyEmail} />
|
||||
</p>
|
||||
<Input
|
||||
{...this.bindField('login')}
|
||||
icon="envelope"
|
||||
color="lightViolet"
|
||||
required
|
||||
placeholder={messages.accountEmail}
|
||||
defaultValue={login}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div data-testid="forgot-password-login">
|
||||
<div className={styles.login}>
|
||||
{login}
|
||||
<span className={styles.editLogin} onClick={this.onClickEdit} data-testid="edit-login" />
|
||||
</div>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.pleasePressButton} />
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Captcha {...this.bindField('captcha')} delay={600} />
|
||||
</div>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.pleasePressButton} />
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Captcha {...this.bindField('captcha')} delay={600} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
serialize() {
|
||||
const data = super.serialize();
|
||||
|
||||
if (!data.login) {
|
||||
data.login = this.getLogin();
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
serialize() {
|
||||
const data = super.serialize();
|
||||
|
||||
getLogin() {
|
||||
const login = getLogin(this.context);
|
||||
const { user } = this.context;
|
||||
if (!data.login) {
|
||||
data.login = this.getLogin();
|
||||
}
|
||||
|
||||
return login || user.username || user.email || '';
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
onClickEdit = async () => {
|
||||
this.setState({
|
||||
isLoginEdit: true,
|
||||
});
|
||||
getLogin() {
|
||||
const login = getLogin(this.context);
|
||||
const { user } = this.context;
|
||||
|
||||
await this.context.requestRedraw();
|
||||
return login || user.username || user.email || '';
|
||||
}
|
||||
|
||||
this.form.focus('login');
|
||||
};
|
||||
onClickEdit = async () => {
|
||||
this.setState({
|
||||
isLoginEdit: true,
|
||||
});
|
||||
|
||||
await this.context.requestRedraw();
|
||||
|
||||
this.form.focus('login');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 8px;
|
||||
color: #aaa;
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 8px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.login {
|
||||
composes: email from '~app/components/auth/password/password.scss';
|
||||
composes: email from '~app/components/auth/password/password.scss';
|
||||
}
|
||||
|
||||
.editLogin {
|
||||
composes: pencil from '~app/components/ui/icons.scss';
|
||||
composes: pencil from '~app/components/ui/icons.scss';
|
||||
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
padding-left: 3px;
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
padding-left: 3px;
|
||||
|
||||
color: #666666;
|
||||
font-size: 10px;
|
||||
color: #666666;
|
||||
font-size: 10px;
|
||||
|
||||
transition: color 0.3s;
|
||||
transition: color 0.3s;
|
||||
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
&:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.helpLinks {
|
||||
margin: 8px 0;
|
||||
position: relative;
|
||||
height: 20px;
|
||||
margin: 8px 0;
|
||||
position: relative;
|
||||
height: 20px;
|
||||
|
||||
color: #444;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"createNewAccount": "Create new account",
|
||||
"loginTitle": "Sign in",
|
||||
"emailOrUsername": "E‑mail or username",
|
||||
"next": "Next"
|
||||
"createNewAccount": "Create new account",
|
||||
"loginTitle": "Sign in",
|
||||
"emailOrUsername": "E‑mail or username",
|
||||
"next": "Next"
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import Body from './LoginBody';
|
||||
import messages from './Login.intl.json';
|
||||
|
||||
export default factory({
|
||||
title: messages.loginTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'green',
|
||||
label: messages.next,
|
||||
},
|
||||
links: {
|
||||
isAvailable: (context) => !context.user.isGuest,
|
||||
label: messages.createNewAccount,
|
||||
},
|
||||
title: messages.loginTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'green',
|
||||
label: messages.next,
|
||||
},
|
||||
links: {
|
||||
isAvailable: (context) => !context.user.isGuest,
|
||||
label: messages.createNewAccount,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,26 +7,21 @@ import { User } from 'app/components/user/reducer';
|
||||
import messages from './Login.intl.json';
|
||||
|
||||
export default class LoginBody extends BaseAuthBody {
|
||||
static displayName = 'LoginBody';
|
||||
static panelId = 'login';
|
||||
static hasGoBack = (state: { user: User }) => {
|
||||
return !state.user.isGuest;
|
||||
};
|
||||
static displayName = 'LoginBody';
|
||||
static panelId = 'login';
|
||||
static hasGoBack = (state: { user: User }) => {
|
||||
return !state.user.isGuest;
|
||||
};
|
||||
|
||||
autoFocusField = 'login';
|
||||
autoFocusField = 'login';
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<Input
|
||||
{...this.bindField('login')}
|
||||
icon="envelope"
|
||||
required
|
||||
placeholder={messages.emailOrUsername}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Input {...this.bindField('login')} icon="envelope" required placeholder={messages.emailOrUsername} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"enterTotp": "Enter code",
|
||||
"description": "In order to sign in this account, you need to enter a one-time password from mobile application"
|
||||
"enterTotp": "Enter code",
|
||||
"description": "In order to sign in this account, you need to enter a one-time password from mobile application"
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import messages from './Mfa.intl.json';
|
||||
import passwordMessages from '../password/Password.intl.json';
|
||||
|
||||
export default factory({
|
||||
title: messages.enterTotp,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'green',
|
||||
label: passwordMessages.signInButton,
|
||||
},
|
||||
title: messages.enterTotp,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'green',
|
||||
label: passwordMessages.signInButton,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,30 +8,30 @@ import styles from './mfa.scss';
|
||||
import messages from './Mfa.intl.json';
|
||||
|
||||
export default class MfaBody extends BaseAuthBody {
|
||||
static panelId = 'mfa';
|
||||
static hasGoBack = true;
|
||||
static panelId = 'mfa';
|
||||
static hasGoBack = true;
|
||||
|
||||
autoFocusField = 'totp';
|
||||
autoFocusField = 'totp';
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<PanelIcon icon="lock" />
|
||||
<PanelIcon icon="lock" />
|
||||
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.description} />
|
||||
</p>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.description} />
|
||||
</p>
|
||||
|
||||
<Input
|
||||
{...this.bindField('totp')}
|
||||
icon="key"
|
||||
required
|
||||
placeholder={messages.enterTotp}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Input
|
||||
{...this.bindField('totp')}
|
||||
icon="key"
|
||||
required
|
||||
placeholder={messages.enterTotp}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 8px;
|
||||
color: #aaa;
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 8px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"passwordTitle": "Enter password",
|
||||
"signInButton": "Sign in",
|
||||
"forgotPassword": "Forgot password",
|
||||
"accountPassword": "Account password",
|
||||
"rememberMe": "Remember me on this device"
|
||||
"passwordTitle": "Enter password",
|
||||
"signInButton": "Sign in",
|
||||
"forgotPassword": "Forgot password",
|
||||
"accountPassword": "Account password",
|
||||
"rememberMe": "Remember me on this device"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import Body from './PasswordBody';
|
||||
import messages from './Password.intl.json';
|
||||
|
||||
export default factory({
|
||||
title: messages.passwordTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'green',
|
||||
label: messages.signInButton,
|
||||
},
|
||||
links: {
|
||||
label: messages.forgotPassword,
|
||||
},
|
||||
title: messages.passwordTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'green',
|
||||
label: messages.signInButton,
|
||||
},
|
||||
links: {
|
||||
label: messages.forgotPassword,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,46 +8,38 @@ import styles from './password.scss';
|
||||
import messages from './Password.intl.json';
|
||||
|
||||
export default class PasswordBody extends BaseAuthBody {
|
||||
static displayName = 'PasswordBody';
|
||||
static panelId = 'password';
|
||||
static hasGoBack = true;
|
||||
static displayName = 'PasswordBody';
|
||||
static panelId = 'password';
|
||||
static hasGoBack = true;
|
||||
|
||||
autoFocusField = 'password';
|
||||
autoFocusField = 'password';
|
||||
|
||||
render() {
|
||||
const { user } = this.context;
|
||||
render() {
|
||||
const { user } = this.context;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<div className={styles.miniProfile}>
|
||||
<div className={styles.avatar}>
|
||||
{user.avatar ? (
|
||||
<img src={user.avatar} />
|
||||
) : (
|
||||
<span className={icons.user} />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.email}>{user.email || user.username}</div>
|
||||
</div>
|
||||
<div className={styles.miniProfile}>
|
||||
<div className={styles.avatar}>
|
||||
{user.avatar ? <img src={user.avatar} /> : <span className={icons.user} />}
|
||||
</div>
|
||||
<div className={styles.email}>{user.email || user.username}</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
{...this.bindField('password')}
|
||||
icon="key"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.accountPassword}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('password')}
|
||||
icon="key"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.accountPassword}
|
||||
/>
|
||||
|
||||
<div className={authStyles.checkboxInput}>
|
||||
<Checkbox
|
||||
{...this.bindField('rememberMe')}
|
||||
defaultChecked
|
||||
label={messages.rememberMe}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className={authStyles.checkboxInput}>
|
||||
<Checkbox {...this.bindField('rememberMe')} defaultChecked label={messages.rememberMe} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
|
||||
.avatar {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
font-size: 90px;
|
||||
line-height: 1;
|
||||
margin: 0 auto;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
font-size: 90px;
|
||||
line-height: 1;
|
||||
margin: 0 auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.email {
|
||||
font-family: $font-family-title;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
font-family: $font-family-title;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
|
||||
margin-bottom: 15px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"permissionsTitle": "Application permissions",
|
||||
"youAuthorizedAs": "You authorized as:",
|
||||
"theAppNeedsAccess1": "This application needs access",
|
||||
"theAppNeedsAccess2": "to your data",
|
||||
"decline": "Decline",
|
||||
"approve": "Approve",
|
||||
"scope_minecraft_server_session": "Authorization data for minecraft server",
|
||||
"scope_offline_access": "Access to your profile data, when you offline",
|
||||
"scope_account_info": "Access to your profile data (except E‑mail)",
|
||||
"scope_account_email": "Access to your E‑mail address"
|
||||
"permissionsTitle": "Application permissions",
|
||||
"youAuthorizedAs": "You authorized as:",
|
||||
"theAppNeedsAccess1": "This application needs access",
|
||||
"theAppNeedsAccess2": "to your data",
|
||||
"decline": "Decline",
|
||||
"approve": "Approve",
|
||||
"scope_minecraft_server_session": "Authorization data for minecraft server",
|
||||
"scope_offline_access": "Access to your profile data, when you offline",
|
||||
"scope_account_info": "Access to your profile data (except E‑mail)",
|
||||
"scope_account_email": "Access to your E‑mail address"
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import messages from './Permissions.intl.json';
|
||||
import Body from './PermissionsBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.permissionsTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'orange',
|
||||
autoFocus: true,
|
||||
label: messages.approve,
|
||||
},
|
||||
links: {
|
||||
label: messages.decline,
|
||||
},
|
||||
title: messages.permissionsTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'orange',
|
||||
autoFocus: true,
|
||||
label: messages.approve,
|
||||
},
|
||||
links: {
|
||||
label: messages.decline,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,58 +8,52 @@ import styles from './permissions.scss';
|
||||
import messages from './Permissions.intl.json';
|
||||
|
||||
export default class PermissionsBody extends BaseAuthBody {
|
||||
static displayName = 'PermissionsBody';
|
||||
static panelId = 'permissions';
|
||||
static displayName = 'PermissionsBody';
|
||||
static panelId = 'permissions';
|
||||
|
||||
render() {
|
||||
const { user } = this.context;
|
||||
const { scopes } = this.context.auth;
|
||||
render() {
|
||||
const { user } = this.context;
|
||||
const { scopes } = this.context.auth;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<PanelBodyHeader>
|
||||
<div className={styles.authInfo}>
|
||||
<div className={styles.authInfoAvatar}>
|
||||
{user.avatar ? (
|
||||
<img src={user.avatar} />
|
||||
) : (
|
||||
<span className={icons.user} />
|
||||
)}
|
||||
<PanelBodyHeader>
|
||||
<div className={styles.authInfo}>
|
||||
<div className={styles.authInfoAvatar}>
|
||||
{user.avatar ? <img src={user.avatar} /> : <span className={icons.user} />}
|
||||
</div>
|
||||
<div className={styles.authInfoTitle}>
|
||||
<Message {...messages.youAuthorizedAs} />
|
||||
</div>
|
||||
<div className={styles.authInfoEmail}>{user.username}</div>
|
||||
</div>
|
||||
</PanelBodyHeader>
|
||||
<div className={styles.permissionsContainer}>
|
||||
<div className={styles.permissionsTitle}>
|
||||
<Message {...messages.theAppNeedsAccess1} />
|
||||
<br />
|
||||
<Message {...messages.theAppNeedsAccess2} />
|
||||
</div>
|
||||
<ul className={styles.permissionsList}>
|
||||
{scopes.map((scope) => {
|
||||
const key = `scope_${scope}`;
|
||||
const message = messages[key];
|
||||
|
||||
return (
|
||||
<li key={key}>
|
||||
{message ? (
|
||||
<Message {...message} />
|
||||
) : (
|
||||
scope.replace(/^\w|_/g, (match) => match.replace('_', ' ').toUpperCase())
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.authInfoTitle}>
|
||||
<Message {...messages.youAuthorizedAs} />
|
||||
</div>
|
||||
<div className={styles.authInfoEmail}>{user.username}</div>
|
||||
</div>
|
||||
</PanelBodyHeader>
|
||||
<div className={styles.permissionsContainer}>
|
||||
<div className={styles.permissionsTitle}>
|
||||
<Message {...messages.theAppNeedsAccess1} />
|
||||
<br />
|
||||
<Message {...messages.theAppNeedsAccess2} />
|
||||
</div>
|
||||
<ul className={styles.permissionsList}>
|
||||
{scopes.map((scope) => {
|
||||
const key = `scope_${scope}`;
|
||||
const message = messages[key];
|
||||
|
||||
return (
|
||||
<li key={key}>
|
||||
{message ? (
|
||||
<Message {...message} />
|
||||
) : (
|
||||
scope.replace(/^\w|_/g, (match) =>
|
||||
match.replace('_', ' ').toUpperCase(),
|
||||
)
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,76 +2,76 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
|
||||
.authInfo {
|
||||
// Отступы сверху и снизу разные т.к. мы ужимаем высоту линии строки с логином на 2 пикселя и из-за этого теряем отступ снизу
|
||||
padding: 5px 20px 7px;
|
||||
text-align: left;
|
||||
// Отступы сверху и снизу разные т.к. мы ужимаем высоту линии строки с логином на 2 пикселя и из-за этого теряем отступ снизу
|
||||
padding: 5px 20px 7px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.authInfoAvatar {
|
||||
$size: 30px;
|
||||
$size: 30px;
|
||||
|
||||
float: left;
|
||||
height: $size;
|
||||
width: $size;
|
||||
font-size: $size;
|
||||
line-height: 1;
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
color: #aaa;
|
||||
float: left;
|
||||
height: $size;
|
||||
width: $size;
|
||||
font-size: $size;
|
||||
line-height: 1;
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
color: #aaa;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.authInfoTitle {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.authInfoEmail {
|
||||
font-family: $font-family-title;
|
||||
font-size: 20px;
|
||||
line-height: 16px;
|
||||
color: #fff;
|
||||
font-family: $font-family-title;
|
||||
font-size: 20px;
|
||||
line-height: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.permissionsContainer {
|
||||
padding: 15px 12px;
|
||||
text-align: left;
|
||||
padding: 15px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.permissionsTitle {
|
||||
font-family: $font-family-title;
|
||||
font-size: 18px;
|
||||
color: #dd8650;
|
||||
padding-bottom: 6px;
|
||||
font-family: $font-family-title;
|
||||
font-size: 18px;
|
||||
color: #dd8650;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.permissionsList {
|
||||
list-style: none;
|
||||
margin-top: 10px;
|
||||
list-style: none;
|
||||
margin-top: 10px;
|
||||
|
||||
li {
|
||||
color: #a9a9a9;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 17px;
|
||||
position: relative;
|
||||
li {
|
||||
color: #a9a9a9;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
padding-bottom: 4px;
|
||||
padding-left: 17px;
|
||||
position: relative;
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '• ';
|
||||
color: lighter($light_violet);
|
||||
font-size: 39px; // ~ 9px
|
||||
line-height: 9px;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: '• ';
|
||||
color: lighter($light_violet);
|
||||
font-size: 39px; // ~ 9px
|
||||
line-height: 9px;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"title": "Restore password",
|
||||
"contactSupport": "Contact support",
|
||||
"messageWasSent": "The recovery code was sent to your account E‑mail.",
|
||||
"messageWasSentTo": "The recovery code was sent to your E‑mail {email}.",
|
||||
"enterCodeBelow": "Please enter the code received into the field below:",
|
||||
"enterNewPasswordBelow": "Enter and repeat new password below:",
|
||||
"change": "Change password",
|
||||
"newPassword": "Enter new password",
|
||||
"newRePassword": "Repeat new password",
|
||||
"enterTheCode": "Enter confirmation code"
|
||||
"title": "Restore password",
|
||||
"contactSupport": "Contact support",
|
||||
"messageWasSent": "The recovery code was sent to your account E‑mail.",
|
||||
"messageWasSentTo": "The recovery code was sent to your E‑mail {email}.",
|
||||
"enterCodeBelow": "Please enter the code received into the field below:",
|
||||
"enterNewPasswordBelow": "Enter and repeat new password below:",
|
||||
"change": "Change password",
|
||||
"newPassword": "Enter new password",
|
||||
"newRePassword": "Repeat new password",
|
||||
"enterTheCode": "Enter confirmation code"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import messages from './RecoverPassword.intl.json';
|
||||
import Body from './RecoverPasswordBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'lightViolet',
|
||||
label: messages.change,
|
||||
},
|
||||
links: {
|
||||
label: messages.contactSupport,
|
||||
},
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'lightViolet',
|
||||
label: messages.change,
|
||||
},
|
||||
links: {
|
||||
label: messages.contactSupport,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,70 +11,67 @@ import messages from './RecoverPassword.intl.json';
|
||||
// TODO: activation code field may be decoupled into common component and reused here and in activation panel
|
||||
|
||||
export default class RecoverPasswordBody extends BaseAuthBody {
|
||||
static displayName = 'RecoverPasswordBody';
|
||||
static panelId = 'recoverPassword';
|
||||
static hasGoBack = true;
|
||||
static displayName = 'RecoverPasswordBody';
|
||||
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;
|
||||
const { key } = this.props.match.params;
|
||||
render() {
|
||||
const { user } = this.context;
|
||||
const { key } = this.props.match.params;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<p className={styles.descriptionText}>
|
||||
{user.maskedEmail ? (
|
||||
<Message
|
||||
{...messages.messageWasSentTo}
|
||||
values={{
|
||||
email: <b>{user.maskedEmail}</b>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Message {...messages.messageWasSent} />
|
||||
)}{' '}
|
||||
<Message {...messages.enterCodeBelow} />
|
||||
</p>
|
||||
<p className={styles.descriptionText}>
|
||||
{user.maskedEmail ? (
|
||||
<Message
|
||||
{...messages.messageWasSentTo}
|
||||
values={{
|
||||
email: <b>{user.maskedEmail}</b>,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Message {...messages.messageWasSent} />
|
||||
)}{' '}
|
||||
<Message {...messages.enterCodeBelow} />
|
||||
</p>
|
||||
|
||||
<Input
|
||||
{...this.bindField('key')}
|
||||
color="lightViolet"
|
||||
center
|
||||
required
|
||||
value={key}
|
||||
readOnly={!!key}
|
||||
autoComplete="off"
|
||||
placeholder={messages.enterTheCode}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('key')}
|
||||
color="lightViolet"
|
||||
center
|
||||
required
|
||||
value={key}
|
||||
readOnly={!!key}
|
||||
autoComplete="off"
|
||||
placeholder={messages.enterTheCode}
|
||||
/>
|
||||
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.enterNewPasswordBelow} />
|
||||
</p>
|
||||
<p className={styles.descriptionText}>
|
||||
<Message {...messages.enterNewPasswordBelow} />
|
||||
</p>
|
||||
|
||||
<Input
|
||||
{...this.bindField('newPassword')}
|
||||
icon="key"
|
||||
color="lightViolet"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.newPassword}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('newPassword')}
|
||||
icon="key"
|
||||
color="lightViolet"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.newPassword}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...this.bindField('newRePassword')}
|
||||
icon="key"
|
||||
color="lightViolet"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.newRePassword}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Input
|
||||
{...this.bindField('newRePassword')}
|
||||
icon="key"
|
||||
color="lightViolet"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.newRePassword}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
|
||||
.descriptionText {
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
color: #aaa;
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -3,40 +3,36 @@ import auth from './reducer';
|
||||
import { setLogin, setAccountSwitcher } from './actions';
|
||||
|
||||
describe('components/auth/reducer', () => {
|
||||
describe('auth:setCredentials', () => {
|
||||
it('should set login', () => {
|
||||
const expectedLogin = 'foo';
|
||||
describe('auth:setCredentials', () => {
|
||||
it('should set login', () => {
|
||||
const expectedLogin = 'foo';
|
||||
|
||||
expect(
|
||||
auth(undefined, setLogin(expectedLogin)).credentials,
|
||||
'to satisfy',
|
||||
{
|
||||
login: expectedLogin,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('auth:setAccountSwitcher', () => {
|
||||
it('should be enabled by default', () =>
|
||||
expect(auth(undefined, {} as any), 'to satisfy', {
|
||||
isSwitcherEnabled: true,
|
||||
}));
|
||||
|
||||
it('should enable switcher', () => {
|
||||
const expectedValue = true;
|
||||
|
||||
expect(auth(undefined, setAccountSwitcher(expectedValue)), 'to satisfy', {
|
||||
isSwitcherEnabled: expectedValue,
|
||||
});
|
||||
expect(auth(undefined, setLogin(expectedLogin)).credentials, 'to satisfy', {
|
||||
login: expectedLogin,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable switcher', () => {
|
||||
const expectedValue = false;
|
||||
describe('auth:setAccountSwitcher', () => {
|
||||
it('should be enabled by default', () =>
|
||||
expect(auth(undefined, {} as any), 'to satisfy', {
|
||||
isSwitcherEnabled: true,
|
||||
}));
|
||||
|
||||
expect(auth(undefined, setAccountSwitcher(expectedValue)), 'to satisfy', {
|
||||
isSwitcherEnabled: expectedValue,
|
||||
});
|
||||
it('should enable switcher', () => {
|
||||
const expectedValue = true;
|
||||
|
||||
expect(auth(undefined, setAccountSwitcher(expectedValue)), 'to satisfy', {
|
||||
isSwitcherEnabled: expectedValue,
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable switcher', () => {
|
||||
const expectedValue = false;
|
||||
|
||||
expect(auth(undefined, setAccountSwitcher(expectedValue)), 'to satisfy', {
|
||||
isSwitcherEnabled: expectedValue,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,173 +3,156 @@ import { RootState } from 'app/reducers';
|
||||
import { Scope } from '../../services/api/oauth';
|
||||
|
||||
import {
|
||||
ErrorAction,
|
||||
CredentialsAction,
|
||||
AccountSwitcherAction,
|
||||
LoadingAction,
|
||||
ClientAction,
|
||||
OAuthAction,
|
||||
ScopesAction,
|
||||
ErrorAction,
|
||||
CredentialsAction,
|
||||
AccountSwitcherAction,
|
||||
LoadingAction,
|
||||
ClientAction,
|
||||
OAuthAction,
|
||||
ScopesAction,
|
||||
} from './actions';
|
||||
|
||||
export interface Credentials {
|
||||
login?: string | null; // By some reasons there is can be null value. Need to investigate.
|
||||
password?: string;
|
||||
rememberMe?: boolean;
|
||||
returnUrl?: string;
|
||||
isRelogin?: boolean;
|
||||
isTotpRequired?: boolean;
|
||||
login?: string | null; // By some reasons there is can be null value. Need to investigate.
|
||||
password?: string;
|
||||
rememberMe?: boolean;
|
||||
returnUrl?: string;
|
||||
isRelogin?: boolean;
|
||||
isTotpRequired?: boolean;
|
||||
}
|
||||
|
||||
type Error = Record<
|
||||
string,
|
||||
| string
|
||||
| {
|
||||
type: string;
|
||||
payload: Record<string, any>;
|
||||
}
|
||||
string,
|
||||
| string
|
||||
| {
|
||||
type: string;
|
||||
payload: Record<string, any>;
|
||||
}
|
||||
> | null;
|
||||
|
||||
export interface Client {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface OAuthState {
|
||||
clientId: string;
|
||||
redirectUrl: string;
|
||||
responseType: string;
|
||||
description?: string;
|
||||
scope: string;
|
||||
prompt: string;
|
||||
loginHint: string;
|
||||
state: string;
|
||||
success?: boolean;
|
||||
code?: string;
|
||||
displayCode?: boolean;
|
||||
acceptRequired?: boolean;
|
||||
clientId: string;
|
||||
redirectUrl: string;
|
||||
responseType: string;
|
||||
description?: string;
|
||||
scope: string;
|
||||
prompt: string;
|
||||
loginHint: string;
|
||||
state: string;
|
||||
success?: boolean;
|
||||
code?: string;
|
||||
displayCode?: boolean;
|
||||
acceptRequired?: boolean;
|
||||
}
|
||||
|
||||
type Scopes = Array<Scope>;
|
||||
|
||||
export interface State {
|
||||
credentials: Credentials;
|
||||
error: Error;
|
||||
isLoading: boolean;
|
||||
isSwitcherEnabled: boolean;
|
||||
client: Client | null;
|
||||
oauth: OAuthState | null;
|
||||
scopes: Scopes;
|
||||
credentials: Credentials;
|
||||
error: Error;
|
||||
isLoading: boolean;
|
||||
isSwitcherEnabled: boolean;
|
||||
client: Client | null;
|
||||
oauth: OAuthState | null;
|
||||
scopes: Scopes;
|
||||
}
|
||||
|
||||
const error: Reducer<State['error'], ErrorAction> = (
|
||||
state = null,
|
||||
{ type, payload },
|
||||
) => {
|
||||
if (type === 'auth:error') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const credentials: Reducer<State['credentials'], CredentialsAction> = (
|
||||
state = {},
|
||||
{ type, payload },
|
||||
) => {
|
||||
if (type === 'auth:setCredentials') {
|
||||
if (payload) {
|
||||
return {
|
||||
...payload,
|
||||
};
|
||||
const error: Reducer<State['error'], ErrorAction> = (state = null, { type, payload }) => {
|
||||
if (type === 'auth:error') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
return state;
|
||||
return state;
|
||||
};
|
||||
|
||||
const isSwitcherEnabled: Reducer<
|
||||
State['isSwitcherEnabled'],
|
||||
AccountSwitcherAction
|
||||
> = (state = true, { type, payload }) => {
|
||||
if (type === 'auth:setAccountSwitcher') {
|
||||
return payload;
|
||||
}
|
||||
const credentials: Reducer<State['credentials'], CredentialsAction> = (state = {}, { type, payload }) => {
|
||||
if (type === 'auth:setCredentials') {
|
||||
if (payload) {
|
||||
return {
|
||||
...payload,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
return {};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const isLoading: Reducer<State['isLoading'], LoadingAction> = (
|
||||
state = false,
|
||||
{ type, payload },
|
||||
const isSwitcherEnabled: Reducer<State['isSwitcherEnabled'], AccountSwitcherAction> = (
|
||||
state = true,
|
||||
{ type, payload },
|
||||
) => {
|
||||
if (type === 'set_loading_state') {
|
||||
return payload;
|
||||
}
|
||||
if (type === 'auth:setAccountSwitcher') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return state;
|
||||
return state;
|
||||
};
|
||||
|
||||
const client: Reducer<State['client'], ClientAction> = (
|
||||
state = null,
|
||||
{ type, payload },
|
||||
) => {
|
||||
if (type === 'set_client') {
|
||||
return payload;
|
||||
}
|
||||
const isLoading: Reducer<State['isLoading'], LoadingAction> = (state = false, { type, payload }) => {
|
||||
if (type === 'set_loading_state') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return state;
|
||||
return state;
|
||||
};
|
||||
|
||||
const client: Reducer<State['client'], ClientAction> = (state = null, { type, payload }) => {
|
||||
if (type === 'set_client') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const oauth: Reducer<State['oauth'], OAuthAction> = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case 'set_oauth':
|
||||
return action.payload;
|
||||
case 'set_oauth_result':
|
||||
return {
|
||||
...(state as OAuthState),
|
||||
...action.payload,
|
||||
};
|
||||
case 'require_permissions_accept':
|
||||
return {
|
||||
...(state as OAuthState),
|
||||
acceptRequired: true,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
switch (action.type) {
|
||||
case 'set_oauth':
|
||||
return action.payload;
|
||||
case 'set_oauth_result':
|
||||
return {
|
||||
...(state as OAuthState),
|
||||
...action.payload,
|
||||
};
|
||||
case 'require_permissions_accept':
|
||||
return {
|
||||
...(state as OAuthState),
|
||||
acceptRequired: true,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const scopes: Reducer<State['scopes'], ScopesAction> = (
|
||||
state = [],
|
||||
{ type, payload },
|
||||
) => {
|
||||
if (type === 'set_scopes') {
|
||||
return payload;
|
||||
}
|
||||
const scopes: Reducer<State['scopes'], ScopesAction> = (state = [], { type, payload }) => {
|
||||
if (type === 'set_scopes') {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return state;
|
||||
return state;
|
||||
};
|
||||
|
||||
export default combineReducers<State>({
|
||||
credentials,
|
||||
error,
|
||||
isLoading,
|
||||
isSwitcherEnabled,
|
||||
client,
|
||||
oauth,
|
||||
scopes,
|
||||
credentials,
|
||||
error,
|
||||
isLoading,
|
||||
isSwitcherEnabled,
|
||||
client,
|
||||
oauth,
|
||||
scopes,
|
||||
});
|
||||
|
||||
export function getLogin(
|
||||
state: RootState | Pick<RootState, 'auth'>,
|
||||
): string | null {
|
||||
return state.auth.credentials.login || null;
|
||||
export function getLogin(state: RootState | Pick<RootState, 'auth'>): string | null {
|
||||
return state.auth.credentials.login || null;
|
||||
}
|
||||
|
||||
export function getCredentials(state: RootState): Credentials {
|
||||
return state.auth.credentials;
|
||||
return state.auth.credentials;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"registerTitle": "Sign Up",
|
||||
"yourNickname": "Your nickname",
|
||||
"yourEmail": "Your E‑mail",
|
||||
"accountPassword": "Account password",
|
||||
"repeatPassword": "Repeat password",
|
||||
"signUpButton": "Register",
|
||||
"acceptRules": "I agree with {link}",
|
||||
"termsOfService": "terms of service"
|
||||
"registerTitle": "Sign Up",
|
||||
"yourNickname": "Your nickname",
|
||||
"yourEmail": "Your E‑mail",
|
||||
"accountPassword": "Account password",
|
||||
"repeatPassword": "Repeat password",
|
||||
"signUpButton": "Register",
|
||||
"acceptRules": "I agree with {link}",
|
||||
"termsOfService": "terms of service"
|
||||
}
|
||||
|
||||
@@ -5,19 +5,19 @@ import messages from './Register.intl.json';
|
||||
import Body from './RegisterBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.registerTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'blue',
|
||||
label: messages.signUpButton,
|
||||
},
|
||||
links: [
|
||||
{
|
||||
label: activationMessages.didNotReceivedEmail,
|
||||
payload: { requestEmail: true },
|
||||
title: messages.registerTitle,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'blue',
|
||||
label: messages.signUpButton,
|
||||
},
|
||||
{
|
||||
label: forgotPasswordMessages.alreadyHaveCode,
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{
|
||||
label: activationMessages.didNotReceivedEmail,
|
||||
payload: { requestEmail: true },
|
||||
},
|
||||
{
|
||||
label: forgotPasswordMessages.alreadyHaveCode,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -11,73 +11,73 @@ import messages from './Register.intl.json';
|
||||
// TODO: password and username can be validate for length and sameness
|
||||
|
||||
export default class RegisterBody extends BaseAuthBody {
|
||||
static panelId = 'register';
|
||||
static panelId = 'register';
|
||||
|
||||
autoFocusField = 'username';
|
||||
autoFocusField = 'username';
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<Input
|
||||
{...this.bindField('username')}
|
||||
icon="user"
|
||||
color="blue"
|
||||
type="text"
|
||||
required
|
||||
placeholder={messages.yourNickname}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('username')}
|
||||
icon="user"
|
||||
color="blue"
|
||||
type="text"
|
||||
required
|
||||
placeholder={messages.yourNickname}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...this.bindField('email')}
|
||||
icon="envelope"
|
||||
color="blue"
|
||||
type="email"
|
||||
required
|
||||
placeholder={messages.yourEmail}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('email')}
|
||||
icon="envelope"
|
||||
color="blue"
|
||||
type="email"
|
||||
required
|
||||
placeholder={messages.yourEmail}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...this.bindField('password')}
|
||||
icon="key"
|
||||
color="blue"
|
||||
type="password"
|
||||
required
|
||||
placeholder={passwordMessages.accountPassword}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('password')}
|
||||
icon="key"
|
||||
color="blue"
|
||||
type="password"
|
||||
required
|
||||
placeholder={passwordMessages.accountPassword}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...this.bindField('rePassword')}
|
||||
icon="key"
|
||||
color="blue"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.repeatPassword}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('rePassword')}
|
||||
icon="key"
|
||||
color="blue"
|
||||
type="password"
|
||||
required
|
||||
placeholder={messages.repeatPassword}
|
||||
/>
|
||||
|
||||
<Captcha {...this.bindField('captcha')} delay={600} />
|
||||
<Captcha {...this.bindField('captcha')} delay={600} />
|
||||
|
||||
<div className={styles.checkboxInput}>
|
||||
<Checkbox
|
||||
{...this.bindField('rulesAgreement')}
|
||||
color="blue"
|
||||
required
|
||||
label={
|
||||
<Message
|
||||
{...messages.acceptRules}
|
||||
values={{
|
||||
link: (
|
||||
<Link to="/rules" target="_blank">
|
||||
<Message {...messages.termsOfService} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className={styles.checkboxInput}>
|
||||
<Checkbox
|
||||
{...this.bindField('rulesAgreement')}
|
||||
color="blue"
|
||||
required
|
||||
label={
|
||||
<Message
|
||||
{...messages.acceptRules}
|
||||
values={{
|
||||
link: (
|
||||
<Link to="/rules" target="_blank">
|
||||
<Message {...messages.termsOfService} />
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"title": "Did not received an E‑mail",
|
||||
"specifyYourEmail": "Please, enter an E‑mail you've registered with and we will send you new activation code",
|
||||
"sendNewEmail": "Send new E‑mail"
|
||||
"title": "Did not received an E‑mail",
|
||||
"specifyYourEmail": "Please, enter an E‑mail you've registered with and we will send you new activation code",
|
||||
"sendNewEmail": "Send new E‑mail"
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import messages from './ResendActivation.intl.json';
|
||||
import Body from './ResendActivationBody';
|
||||
|
||||
export default factory({
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'blue',
|
||||
label: messages.sendNewEmail,
|
||||
},
|
||||
links: {
|
||||
label: forgotPasswordMessages.alreadyHaveCode,
|
||||
},
|
||||
title: messages.title,
|
||||
body: Body,
|
||||
footer: {
|
||||
color: 'blue',
|
||||
label: messages.sendNewEmail,
|
||||
},
|
||||
links: {
|
||||
label: forgotPasswordMessages.alreadyHaveCode,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,33 +8,33 @@ import styles from './resendActivation.scss';
|
||||
import messages from './ResendActivation.intl.json';
|
||||
|
||||
export default class ResendActivation extends BaseAuthBody {
|
||||
static displayName = 'ResendActivation';
|
||||
static panelId = 'resendActivation';
|
||||
static hasGoBack = true;
|
||||
static displayName = 'ResendActivation';
|
||||
static panelId = 'resendActivation';
|
||||
static hasGoBack = true;
|
||||
|
||||
autoFocusField = 'email';
|
||||
autoFocusField = 'email';
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderErrors()}
|
||||
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.specifyYourEmail} />
|
||||
</div>
|
||||
<div className={styles.description}>
|
||||
<Message {...messages.specifyYourEmail} />
|
||||
</div>
|
||||
|
||||
<Input
|
||||
{...this.bindField('email')}
|
||||
icon="envelope"
|
||||
color="blue"
|
||||
type="email"
|
||||
required
|
||||
placeholder={registerMessages.yourEmail}
|
||||
defaultValue={this.context.user.email}
|
||||
/>
|
||||
<Input
|
||||
{...this.bindField('email')}
|
||||
icon="envelope"
|
||||
color="blue"
|
||||
type="email"
|
||||
required
|
||||
placeholder={registerMessages.yourEmail}
|
||||
defaultValue={this.context.user.email}
|
||||
/>
|
||||
|
||||
<Captcha {...this.bindField('captcha')} delay={600} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<Captcha {...this.bindField('captcha')} delay={600} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import '~app/components/ui/fonts.scss';
|
||||
|
||||
.description {
|
||||
font-family: $font-family-title;
|
||||
margin: 5px 0 19px;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
font-family: $font-family-title;
|
||||
margin: 5px 0 19px;
|
||||
line-height: 1.4;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user