diff --git a/src/components/auth/PanelTransition.jsx b/src/components/auth/PanelTransition.jsx
index bf4699a..dae99df 100644
--- a/src/components/auth/PanelTransition.jsx
+++ b/src/components/auth/PanelTransition.jsx
@@ -33,6 +33,7 @@ const contexts = [
['login', 'password', 'forgotPassword', 'recoverPassword'],
['register', 'activation', 'resendActivation'],
['changePassword'],
+ ['acceptRules'],
['permissions']
];
diff --git a/src/components/auth/README.md b/src/components/auth/README.md
new file mode 100644
index 0000000..8dd2b60
--- /dev/null
+++ b/src/components/auth/README.md
@@ -0,0 +1,15 @@
+# How to add new auth panel
+
+To add new panel you need to:
+
+* 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`
+* create panel component at `components/auth/[panelId]`
+* add new context in `components/auth/PanelTransition`
+* connect component to `routes`
+* whatever else you need
+
+# TODO
+
+This flow must be simplified
diff --git a/src/components/auth/acceptRules/AcceptRules.intl.json b/src/components/auth/acceptRules/AcceptRules.intl.json
new file mode 100644
index 0000000..ec23675
--- /dev/null
+++ b/src/components/auth/acceptRules/AcceptRules.intl.json
@@ -0,0 +1,6 @@
+{
+ "title": "Accept new rules",
+ "accept": "Accept",
+ "decline": "Decline",
+ "description": "We have updated our {link}. In order to get access to accounts.ely.by service, you need to accept them."
+}
diff --git a/src/components/auth/acceptRules/AcceptRules.jsx b/src/components/auth/acceptRules/AcceptRules.jsx
new file mode 100644
index 0000000..67504b5
--- /dev/null
+++ b/src/components/auth/acceptRules/AcceptRules.jsx
@@ -0,0 +1,17 @@
+import factory from 'components/auth/factory';
+
+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.decline
+ }
+});
diff --git a/src/components/auth/acceptRules/AcceptRulesBody.jsx b/src/components/auth/acceptRules/AcceptRulesBody.jsx
new file mode 100644
index 0000000..2515ed4
--- /dev/null
+++ b/src/components/auth/acceptRules/AcceptRulesBody.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+
+import { FormattedMessage as Message } from 'react-intl';
+import { Link } from 'react-router';
+
+import icons from 'components/ui/icons.scss';
+import BaseAuthBody from 'components/auth/BaseAuthBody';
+import registerMessages from 'components/auth/register/Register.intl.json';
+
+import styles from './acceptRules.scss';
+import messages from './AcceptRules.intl.json';
+
+export default class AcceptRulesBody extends BaseAuthBody {
+ static displayName = 'AcceptRulesBody';
+ static panelId = 'acceptRules';
+
+ render() {
+ return (
+
+ {this.renderErrors()}
+
+
+
+
+
+
+
+
+
+ )
+ }} />
+
+
+ );
+ }
+}
diff --git a/src/components/auth/acceptRules/acceptRules.scss b/src/components/auth/acceptRules/acceptRules.scss
new file mode 100644
index 0000000..1daf163
--- /dev/null
+++ b/src/components/auth/acceptRules/acceptRules.scss
@@ -0,0 +1,16 @@
+@import '~components/ui/colors.scss';
+
+.descriptionText {
+ 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;
+}
diff --git a/src/components/auth/actions.js b/src/components/auth/actions.js
index a1ad175..fb122e6 100644
--- a/src/components/auth/actions.js
+++ b/src/components/auth/actions.js
@@ -1,6 +1,6 @@
import { routeActions } from 'react-router-redux';
-import { updateUser, logout as logoutUser, changePassword as changeUserPassword, authenticate } from 'components/user/actions';
+import { updateUser, logout as logoutUser, changePassword as changeUserPassword, acceptRules as userAcceptRules, authenticate } from 'components/user/actions';
import authentication from 'services/api/authentication';
import oauth from 'services/api/oauth';
import signup from 'services/api/signup';
@@ -46,6 +46,13 @@ export function changePassword({
);
}
+export function acceptRules() {
+ return wrapInLoader((dispatch) =>
+ dispatch(userAcceptRules())
+ .catch(validationErrorsHandler(dispatch))
+ );
+}
+
export function forgotPassword({
login = ''
}) {
diff --git a/src/components/user/User.js b/src/components/user/User.js
index da86249..726fbb9 100644
--- a/src/components/user/User.js
+++ b/src/components/user/User.js
@@ -4,7 +4,7 @@ const KEY_USER = 'user';
export default class User {
/**
- * @param {Object|string|undefined} data plain object or jwt token or empty to load from storage
+ * @param {object|string|undefined} data plain object or jwt token or empty to load from storage
*
* @return {User}
*/
@@ -30,6 +30,7 @@ export default class User {
goal: null, // the goal with wich user entered site
isGuest: true,
isActive: false,
+ shouldAcceptRules: false, // whether user need to review updated rules
shouldChangePassword: false, // TODO: нужно ещё пробросить причину необходимости смены
passwordChangedAt: null,
hasMojangUsernameCollision: false,
diff --git a/src/components/user/actions.js b/src/components/user/actions.js
index e02fb72..65dbea8 100644
--- a/src/components/user/actions.js
+++ b/src/components/user/actions.js
@@ -89,6 +89,19 @@ export function changePassword({
;
}
+export function acceptRules() {
+ return (dispatch) =>
+ accounts.acceptRules()
+ .then((resp) => {
+ dispatch(updateUser({
+ shouldAcceptRules: false
+ }));
+
+ return resp;
+ })
+ ;
+}
+
let middlewareAdded = false;
export function authenticate(token, refreshToken) { // TODO: this action, probably, belongs to components/auth
return (dispatch, getState) => {
diff --git a/src/routes.js b/src/routes.js
index 972fd9e..962650f 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -21,6 +21,7 @@ import Activation from 'components/auth/activation/Activation';
import ResendActivation from 'components/auth/resendActivation/ResendActivation';
import Password from 'components/auth/password/Password';
import ChangePassword from 'components/auth/changePassword/ChangePassword';
+import AcceptRules from 'components/auth/acceptRules/AcceptRules';
import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
import RecoverPassword from 'components/auth/recoverPassword/RecoverPassword';
import Finish from 'components/auth/finish/Finish';
@@ -60,6 +61,7 @@ export default function routesFactory(store) {
+
diff --git a/src/services/api/accounts.js b/src/services/api/accounts.js
index 03d7c58..34dac16 100644
--- a/src/services/api/accounts.js
+++ b/src/services/api/accounts.js
@@ -17,6 +17,10 @@ export default {
);
},
+ acceptRules() {
+ return request.post('/api/accounts/accept-rules');
+ },
+
changeUsername({
username = '',
password = ''
diff --git a/src/services/authFlow/AcceptRulesState.js b/src/services/authFlow/AcceptRulesState.js
new file mode 100644
index 0000000..755302d
--- /dev/null
+++ b/src/services/authFlow/AcceptRulesState.js
@@ -0,0 +1,23 @@
+import AbstractState from './AbstractState';
+import CompleteState from './CompleteState';
+
+export default class AcceptRulesState extends AbstractState {
+ enter(context) {
+ const {user} = context.getState();
+
+ if (user.shouldAcceptRules) {
+ context.navigate('/accept-rules');
+ } else {
+ context.setState(new CompleteState());
+ }
+ }
+
+ resolve(context) {
+ context.run('acceptRules')
+ .then(() => context.setState(new CompleteState()));
+ }
+
+ reject(context) {
+ context.run('logout');
+ }
+}
diff --git a/src/services/authFlow/AuthFlow.js b/src/services/authFlow/AuthFlow.js
index c46c4db..c7f4e5b 100644
--- a/src/services/authFlow/AuthFlow.js
+++ b/src/services/authFlow/AuthFlow.js
@@ -143,6 +143,7 @@ export default class AuthFlow {
case '/':
case '/login':
case '/password':
+ case '/accept-rules':
case '/change-password':
case '/oauth/permissions':
case '/oauth/finish':
diff --git a/src/services/authFlow/CompleteState.js b/src/services/authFlow/CompleteState.js
index d7ab333..c5d68b9 100644
--- a/src/services/authFlow/CompleteState.js
+++ b/src/services/authFlow/CompleteState.js
@@ -3,6 +3,7 @@ import LoginState from './LoginState';
import PermissionsState from './PermissionsState';
import ActivationState from './ActivationState';
import ChangePasswordState from './ChangePasswordState';
+import AcceptRulesState from './AcceptRulesState';
import FinishState from './FinishState';
export default class CompleteState extends AbstractState {
@@ -19,6 +20,8 @@ export default class CompleteState extends AbstractState {
context.setState(new LoginState());
} else if (!user.isActive) {
context.setState(new ActivationState());
+ } else if (user.shouldAcceptRules) {
+ context.setState(new AcceptRulesState());
} else if (user.shouldChangePassword) {
context.setState(new ChangePasswordState());
} else if (auth.oauth && auth.oauth.clientId) {
diff --git a/tests/services/authFlow/AcceptRulesState.test.js b/tests/services/authFlow/AcceptRulesState.test.js
new file mode 100644
index 0000000..f0f05f1
--- /dev/null
+++ b/tests/services/authFlow/AcceptRulesState.test.js
@@ -0,0 +1,88 @@
+import AcceptRulesState from 'services/authFlow/AcceptRulesState';
+import CompleteState from 'services/authFlow/CompleteState';
+
+import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
+
+describe('AcceptRulesState', () => {
+ let state;
+ let context;
+ let mock;
+
+ beforeEach(() => {
+ state = new AcceptRulesState();
+
+ const data = bootstrap();
+ context = data.context;
+ mock = data.mock;
+ });
+
+ afterEach(() => {
+ mock.verify();
+ });
+
+ describe('#enter', () => {
+ it('should navigate to /accept-rules', () => {
+ context.getState.returns({
+ user: {
+ shouldAcceptRules: true,
+ isGuest: false
+ }
+ });
+
+ expectNavigate(mock, '/accept-rules');
+
+ state.enter(context);
+ });
+
+ it('should transition to complete state if rules accepted', () => {
+ context.getState.returns({
+ user: {
+ shouldAcceptRules: false,
+ isGuest: false
+ }
+ });
+
+ expectState(mock, CompleteState);
+
+ state.enter(context);
+ });
+ });
+
+ describe('#resolve', () => {
+ it('should call acceptRules', () => {
+ expectRun(mock, 'acceptRules').returns({then() {}});
+
+ state.resolve(context);
+ });
+
+ it('should transition to complete state on success', () => {
+ const promise = Promise.resolve();
+
+ mock.expects('run').returns(promise);
+ expectState(mock, CompleteState);
+
+ state.resolve(context);
+
+ return promise;
+ });
+
+ it('should NOT transition to complete state on fail', () => {
+ const promise = Promise.reject();
+
+ mock.expects('run').returns(promise);
+ mock.expects('setState').never();
+
+ state.resolve(context);
+
+ return promise.catch(mock.verify.bind(mock));
+ });
+ });
+
+ describe('#reject', () => {
+ it('should logout', () => {
+ expectRun(mock, 'logout');
+
+ state.reject(context);
+ });
+ });
+});
diff --git a/tests/services/authFlow/AuthFlow.test.js b/tests/services/authFlow/AuthFlow.test.js
index 21e9f07..2b2c6d2 100644
--- a/tests/services/authFlow/AuthFlow.test.js
+++ b/tests/services/authFlow/AuthFlow.test.js
@@ -5,6 +5,7 @@ import AbstractState from 'services/authFlow/AbstractState';
import OAuthState from 'services/authFlow/OAuthState';
import RegisterState from 'services/authFlow/RegisterState';
+import AcceptRulesState from 'services/authFlow/AcceptRulesState';
import RecoverPasswordState from 'services/authFlow/RecoverPasswordState';
import ForgotPasswordState from 'services/authFlow/ForgotPasswordState';
import ActivationState from 'services/authFlow/ActivationState';
@@ -178,6 +179,7 @@ describe('AuthFlow', () => {
'/login': LoginState,
'/password': LoginState,
'/change-password': LoginState,
+ '/accept-rules': LoginState,
'/oauth/permissions': LoginState,
'/oauth/finish': LoginState,
'/oauth2/v1': OAuthState,
diff --git a/tests/services/authFlow/CompleteState.test.js b/tests/services/authFlow/CompleteState.test.js
index e77de5d..4d0977f 100644
--- a/tests/services/authFlow/CompleteState.test.js
+++ b/tests/services/authFlow/CompleteState.test.js
@@ -4,6 +4,7 @@ import CompleteState from 'services/authFlow/CompleteState';
import LoginState from 'services/authFlow/LoginState';
import ActivationState from 'services/authFlow/ActivationState';
import ChangePasswordState from 'services/authFlow/ChangePasswordState';
+import AcceptRulesState from 'services/authFlow/AcceptRulesState';
import FinishState from 'services/authFlow/FinishState';
import PermissionsState from 'services/authFlow/PermissionsState';
@@ -96,6 +97,35 @@ describe('CompleteState', () => {
state.enter(context);
});
+ it('should transition to accept-rules if shouldAcceptRules', () => {
+ context.getState.returns({
+ user: {
+ shouldAcceptRules: true,
+ isActive: true,
+ isGuest: false
+ },
+ auth: {}
+ });
+
+ expectState(mock, AcceptRulesState);
+
+ state.enter(context);
+ });
+
+ it('should transition to activation with higher priority than shouldAcceptRules', () => {
+ context.getState.returns({
+ user: {
+ shouldAcceptRules: true,
+ isGuest: false
+ },
+ auth: {}
+ });
+
+ expectState(mock, ActivationState);
+
+ state.enter(context);
+ });
+
it('should transition to finish state if code is present', () => {
context.getState.returns({
user: {