mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
#97: frontend for repeat activation functionality
This commit is contained in:
@@ -31,7 +31,7 @@ const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5};
|
|||||||
*/
|
*/
|
||||||
const contexts = [
|
const contexts = [
|
||||||
['login', 'password', 'forgotPassword', 'recoverPassword'],
|
['login', 'password', 'forgotPassword', 'recoverPassword'],
|
||||||
['register', 'activation'],
|
['register', 'activation', 'resendActivation'],
|
||||||
['changePassword'],
|
['changePassword'],
|
||||||
['permissions']
|
['permissions']
|
||||||
];
|
];
|
||||||
|
@@ -126,7 +126,17 @@ export function activate({key = ''}) {
|
|||||||
|
|
||||||
return dispatch(authenticate(resp.jwt));
|
return dispatch(authenticate(resp.jwt));
|
||||||
})
|
})
|
||||||
.catch(validationErrorsHandler(dispatch, '/reactivate'))
|
.catch(validationErrorsHandler(dispatch, '/resend-activation'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resendActivation({email = ''}) {
|
||||||
|
return wrapInLoader((dispatch) =>
|
||||||
|
request.post(
|
||||||
|
'/api/signup/repeat-message',
|
||||||
|
{email}
|
||||||
|
)
|
||||||
|
.catch(validationErrorsHandler(dispatch))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +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"
|
||||||
|
}
|
16
src/components/auth/resendActivation/ResendActivation.jsx
Normal file
16
src/components/auth/resendActivation/ResendActivation.jsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Button } from 'components/ui/form';
|
||||||
|
import AuthTitle from 'components/auth/AuthTitle';
|
||||||
|
|
||||||
|
import messages from './ResendActivation.intl.json';
|
||||||
|
import Body from './ResendActivationBody';
|
||||||
|
|
||||||
|
export default function ResendActivation() {
|
||||||
|
return {
|
||||||
|
Title: () => <AuthTitle title={messages.title} />,
|
||||||
|
Body,
|
||||||
|
Footer: () => <Button color="blue" label={messages.sendNewEmail} type="submit" />,
|
||||||
|
Links: () => null
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
|
||||||
|
import { Input } from 'components/ui/form';
|
||||||
|
import registerMessages from 'components/auth/register/Register.intl.json';
|
||||||
|
|
||||||
|
import BaseAuthBody from 'components/auth/BaseAuthBody';
|
||||||
|
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;
|
||||||
|
|
||||||
|
autoFocusField = 'email';
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderErrors()}
|
||||||
|
|
||||||
|
<div className={styles.description}>
|
||||||
|
<Message {...messages.specifyYourEmail} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.formRow}>
|
||||||
|
<Input {...this.bindField('email')}
|
||||||
|
icon="envelope"
|
||||||
|
color="blue"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
placeholder={registerMessages.yourEmail}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
@import '~components/ui/fonts.scss';
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-family: $font-family-title;
|
||||||
|
margin: 5px 0 19px;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
@@ -57,6 +57,9 @@
|
|||||||
"components.auth.register.termsOfService": "terms of service",
|
"components.auth.register.termsOfService": "terms of service",
|
||||||
"components.auth.register.yourEmail": "Your E-mail",
|
"components.auth.register.yourEmail": "Your E-mail",
|
||||||
"components.auth.register.yourNickname": "Your nickname",
|
"components.auth.register.yourNickname": "Your nickname",
|
||||||
|
"components.auth.resendActivation.sendNewEmail": "Send new E-mail",
|
||||||
|
"components.auth.resendActivation.specifyYourEmail": "Please, enter an E-mail you've registered with and we will send you new activation code.",
|
||||||
|
"components.auth.resendActivation.title": "Did not received an E-mail",
|
||||||
"components.contact.email": "E-mail",
|
"components.contact.email": "E-mail",
|
||||||
"components.contact.message": "Message",
|
"components.contact.message": "Message",
|
||||||
"components.contact.send": "Send",
|
"components.contact.send": "Send",
|
||||||
|
@@ -57,6 +57,9 @@
|
|||||||
"components.auth.register.termsOfService": "правилами сервиса",
|
"components.auth.register.termsOfService": "правилами сервиса",
|
||||||
"components.auth.register.yourEmail": "Ваш E-mail",
|
"components.auth.register.yourEmail": "Ваш E-mail",
|
||||||
"components.auth.register.yourNickname": "Желаемый ник",
|
"components.auth.register.yourNickname": "Желаемый ник",
|
||||||
|
"components.auth.resendActivation.sendNewEmail": "Отправить новое письмо",
|
||||||
|
"components.auth.resendActivation.specifyYourEmail": "Укажите здесь ваш регистрационный E-mail адрес и мы вышлем на него новое письмо с кодом активации аккаунта",
|
||||||
|
"components.auth.resendActivation.title": "Не получил письмо",
|
||||||
"components.contact.email": "E-mail",
|
"components.contact.email": "E-mail",
|
||||||
"components.contact.message": "Сообщение",
|
"components.contact.message": "Сообщение",
|
||||||
"components.contact.send": "Отправить",
|
"components.contact.send": "Отправить",
|
||||||
|
@@ -17,6 +17,7 @@ import Register from 'components/auth/register/Register';
|
|||||||
import Login from 'components/auth/login/Login';
|
import Login from 'components/auth/login/Login';
|
||||||
import Permissions from 'components/auth/permissions/Permissions';
|
import Permissions from 'components/auth/permissions/Permissions';
|
||||||
import Activation from 'components/auth/activation/Activation';
|
import Activation from 'components/auth/activation/Activation';
|
||||||
|
import ResendActivation from 'components/auth/resendActivation/ResendActivation';
|
||||||
import Password from 'components/auth/password/Password';
|
import Password from 'components/auth/password/Password';
|
||||||
import ChangePassword from 'components/auth/changePassword/ChangePassword';
|
import ChangePassword from 'components/auth/changePassword/ChangePassword';
|
||||||
import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
|
import ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
|
||||||
@@ -55,6 +56,7 @@ export default function routesFactory(store) {
|
|||||||
<Route path="/password" components={new Password()} {...startAuthFlow} />
|
<Route path="/password" components={new Password()} {...startAuthFlow} />
|
||||||
<Route path="/register" components={new Register()} {...startAuthFlow} />
|
<Route path="/register" components={new Register()} {...startAuthFlow} />
|
||||||
<Route path="/activation" components={new Activation()} {...startAuthFlow} />
|
<Route path="/activation" components={new Activation()} {...startAuthFlow} />
|
||||||
|
<Route path="/resend-activation" components={new ResendActivation()} {...startAuthFlow} />
|
||||||
<Route path="/oauth/permissions" components={new Permissions()} {...startAuthFlow} />
|
<Route path="/oauth/permissions" components={new Permissions()} {...startAuthFlow} />
|
||||||
<Route path="/oauth/finish" component={Finish} {...startAuthFlow} />
|
<Route path="/oauth/finish" component={Finish} {...startAuthFlow} />
|
||||||
<Route path="/change-password" components={new ChangePassword()} {...startAuthFlow} />
|
<Route path="/change-password" components={new ChangePassword()} {...startAuthFlow} />
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import AbstractState from './AbstractState';
|
import AbstractState from './AbstractState';
|
||||||
import CompleteState from './CompleteState';
|
import CompleteState from './CompleteState';
|
||||||
|
import ResendActivationState from './ResendActivationState';
|
||||||
|
|
||||||
export default class ActivationState extends AbstractState {
|
export default class ActivationState extends AbstractState {
|
||||||
enter(context) {
|
enter(context) {
|
||||||
@@ -16,4 +17,8 @@ export default class ActivationState extends AbstractState {
|
|||||||
context.run('activate', payload)
|
context.run('activate', payload)
|
||||||
.then(() => context.setState(new CompleteState()));
|
.then(() => context.setState(new CompleteState()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reject(context) {
|
||||||
|
context.setState(new ResendActivationState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import LoginState from './LoginState';
|
|||||||
import OAuthState from './OAuthState';
|
import OAuthState from './OAuthState';
|
||||||
import ForgotPasswordState from './ForgotPasswordState';
|
import ForgotPasswordState from './ForgotPasswordState';
|
||||||
import RecoverPasswordState from './RecoverPasswordState';
|
import RecoverPasswordState from './RecoverPasswordState';
|
||||||
|
import ResendActivationState from './ResendActivationState';
|
||||||
|
|
||||||
// TODO: a way to unload service (when we are on account page)
|
// TODO: a way to unload service (when we are on account page)
|
||||||
|
|
||||||
@@ -99,6 +100,10 @@ export default class AuthFlow {
|
|||||||
this.setState(new ForgotPasswordState());
|
this.setState(new ForgotPasswordState());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '/resend-activation':
|
||||||
|
this.setState(new ResendActivationState());
|
||||||
|
break;
|
||||||
|
|
||||||
case '/':
|
case '/':
|
||||||
case '/login':
|
case '/login':
|
||||||
case '/password':
|
case '/password':
|
||||||
|
24
src/services/authFlow/ResendActivationState.js
Normal file
24
src/services/authFlow/ResendActivationState.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import AbstractState from './AbstractState';
|
||||||
|
import CompleteState from './CompleteState';
|
||||||
|
import ActivationState from './ActivationState';
|
||||||
|
|
||||||
|
export default class ResendActivationState extends AbstractState {
|
||||||
|
enter(context) {
|
||||||
|
const {user} = context.getState();
|
||||||
|
|
||||||
|
if (user.isActive) {
|
||||||
|
context.setState(new CompleteState());
|
||||||
|
} else {
|
||||||
|
context.navigate('/repeat-message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(context, payload) {
|
||||||
|
context.run('resendActivation', payload)
|
||||||
|
.then(() => context.setState(new CompleteState()));
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(context) {
|
||||||
|
context.setState(new ActivationState());
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import ActivationState from 'services/authFlow/ActivationState';
|
import ActivationState from 'services/authFlow/ActivationState';
|
||||||
import CompleteState from 'services/authFlow/CompleteState';
|
import CompleteState from 'services/authFlow/CompleteState';
|
||||||
|
import ResendActivationState from 'services/authFlow/ResendActivationState';
|
||||||
|
|
||||||
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||||
|
|
||||||
@@ -83,4 +84,12 @@ describe('ActivationState', () => {
|
|||||||
return promise.catch(mock.verify.bind(mock));
|
return promise.catch(mock.verify.bind(mock));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#reject', () => {
|
||||||
|
it('should transition to resend-activation', () => {
|
||||||
|
expectState(mock, ResendActivationState);
|
||||||
|
|
||||||
|
state.reject(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -84,6 +84,17 @@ describe('RecoverPasswordState', () => {
|
|||||||
|
|
||||||
return promise;
|
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', () => {
|
describe('#reject', () => {
|
||||||
|
95
tests/services/authFlow/ResendActivationState.test.js
Normal file
95
tests/services/authFlow/ResendActivationState.test.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import ResendActivationState from 'services/authFlow/ResendActivationState';
|
||||||
|
import CompleteState from 'services/authFlow/CompleteState';
|
||||||
|
import ActivationState from 'services/authFlow/ActivationState';
|
||||||
|
|
||||||
|
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||||
|
|
||||||
|
describe('ResendActivationState', () => {
|
||||||
|
let state;
|
||||||
|
let context;
|
||||||
|
let mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
state = new ResendActivationState();
|
||||||
|
|
||||||
|
const data = bootstrap();
|
||||||
|
context = data.context;
|
||||||
|
mock = data.mock;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mock.verify();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#enter', () => {
|
||||||
|
it('should navigate to /resend-activation', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isGuest: false,
|
||||||
|
isActive: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectNavigate(mock, '/resend-activation');
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transition to complete state if account activated', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isGuest: false,
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expectState(mock, CompleteState);
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolve', () => {
|
||||||
|
it('should call resendActivation with payload', () => {
|
||||||
|
const payload = {email: 'foo@bar.com'};
|
||||||
|
|
||||||
|
expectRun(
|
||||||
|
mock,
|
||||||
|
'resendActivation',
|
||||||
|
sinon.match.same(payload)
|
||||||
|
).returns({then() {}});
|
||||||
|
|
||||||
|
state.resolve(context, payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
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('#goBack', () => {
|
||||||
|
it('should transition to resend-activation', () => {
|
||||||
|
expectState(mock, ActivationState);
|
||||||
|
|
||||||
|
state.goBack(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user