mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-27 23:40:28 +05:30
#97: frontend for repeat activation functionality
This commit is contained in:
parent
6d44e43f5d
commit
57f3134cd8
@ -31,7 +31,7 @@ const changeContextSpringConfig = {stiffness: 500, damping: 20, precision: 0.5};
|
||||
*/
|
||||
const contexts = [
|
||||
['login', 'password', 'forgotPassword', 'recoverPassword'],
|
||||
['register', 'activation'],
|
||||
['register', 'activation', 'resendActivation'],
|
||||
['changePassword'],
|
||||
['permissions']
|
||||
];
|
||||
|
@ -126,7 +126,17 @@ export function activate({key = ''}) {
|
||||
|
||||
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.yourEmail": "Your E-mail",
|
||||
"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.message": "Message",
|
||||
"components.contact.send": "Send",
|
||||
|
@ -57,6 +57,9 @@
|
||||
"components.auth.register.termsOfService": "правилами сервиса",
|
||||
"components.auth.register.yourEmail": "Ваш E-mail",
|
||||
"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.message": "Сообщение",
|
||||
"components.contact.send": "Отправить",
|
||||
|
@ -17,6 +17,7 @@ import Register from 'components/auth/register/Register';
|
||||
import Login from 'components/auth/login/Login';
|
||||
import Permissions from 'components/auth/permissions/Permissions';
|
||||
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 ForgotPassword from 'components/auth/forgotPassword/ForgotPassword';
|
||||
@ -55,6 +56,7 @@ export default function routesFactory(store) {
|
||||
<Route path="/password" components={new Password()} {...startAuthFlow} />
|
||||
<Route path="/register" components={new Register()} {...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/finish" component={Finish} {...startAuthFlow} />
|
||||
<Route path="/change-password" components={new ChangePassword()} {...startAuthFlow} />
|
||||
|
@ -1,5 +1,6 @@
|
||||
import AbstractState from './AbstractState';
|
||||
import CompleteState from './CompleteState';
|
||||
import ResendActivationState from './ResendActivationState';
|
||||
|
||||
export default class ActivationState extends AbstractState {
|
||||
enter(context) {
|
||||
@ -16,4 +17,8 @@ export default class ActivationState extends AbstractState {
|
||||
context.run('activate', payload)
|
||||
.then(() => context.setState(new CompleteState()));
|
||||
}
|
||||
|
||||
reject(context) {
|
||||
context.setState(new ResendActivationState());
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import LoginState from './LoginState';
|
||||
import OAuthState from './OAuthState';
|
||||
import ForgotPasswordState from './ForgotPasswordState';
|
||||
import RecoverPasswordState from './RecoverPasswordState';
|
||||
import ResendActivationState from './ResendActivationState';
|
||||
|
||||
// TODO: a way to unload service (when we are on account page)
|
||||
|
||||
@ -99,6 +100,10 @@ export default class AuthFlow {
|
||||
this.setState(new ForgotPasswordState());
|
||||
break;
|
||||
|
||||
case '/resend-activation':
|
||||
this.setState(new ResendActivationState());
|
||||
break;
|
||||
|
||||
case '/':
|
||||
case '/login':
|
||||
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 CompleteState from 'services/authFlow/CompleteState';
|
||||
import ResendActivationState from 'services/authFlow/ResendActivationState';
|
||||
|
||||
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||
|
||||
@ -83,4 +84,12 @@ describe('ActivationState', () => {
|
||||
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;
|
||||
});
|
||||
|
||||
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', () => {
|
||||
|
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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user