Merge Request #59
← To merge requests
From
forgot_password
into
master
Commits (2)
-
After clicking on "Forgot your password?", the user receives an email with a link to create a new password.
Showing
16 changed files
Show diff stats
src/app/login/auth.controller.spec.ts
1 | import {AuthController} from "./auth.controller"; | 1 | import {AuthController} from "./auth.controller"; |
2 | import {AuthService} from "./auth.service"; | 2 | import {AuthService} from "./auth.service"; |
3 | +import * as helpers from "./../../spec/helpers"; | ||
3 | 4 | ||
4 | describe("Controllers", () => { | 5 | describe("Controllers", () => { |
5 | 6 | ||
6 | 7 | ||
7 | describe("AuthController", () => { | 8 | describe("AuthController", () => { |
8 | 9 | ||
9 | - it("calls authenticate on AuthService when login called", () => { | 10 | + let $modal: any; |
11 | + let notificationService = helpers.mocks.notificationService; | ||
12 | + notificationService.success = jasmine.createSpy('info'); | ||
13 | + notificationService.error = jasmine.createSpy('error'); | ||
10 | 14 | ||
11 | - // creating a Mock AuthService | ||
12 | - let AuthServiceMock: AuthService = jasmine.createSpyObj("AuthService", ["login"]); | 15 | + let authController: any; |
16 | + let AuthServiceMock: AuthService; | ||
17 | + let username: string; | ||
13 | 18 | ||
14 | - // pass AuthServiceMock into the constructor | ||
15 | - let authController = new AuthController(null, null, AuthServiceMock); | 19 | + beforeEach(() => { |
20 | + $modal = helpers.mocks.$modal; | ||
21 | + AuthServiceMock = jasmine.createSpyObj("AuthService", ["login", | ||
22 | +"forgotPassword"]); | ||
23 | + authController = new AuthController(null, null, AuthServiceMock, $modal, notificationService); | ||
24 | + }); | ||
16 | 25 | ||
17 | - // setup of authController -> set the credentials instance property | ||
18 | - let credentials = { username: "username", password: "password" }; | ||
19 | 26 | ||
27 | + it("calls authenticate on AuthService when login called", () => { | ||
28 | + let credentials = { username: "username", password: "password" }; | ||
20 | authController.credentials = credentials; | 29 | authController.credentials = credentials; |
21 | - | ||
22 | - // calls the authController login method | ||
23 | authController.login(); | 30 | authController.login(); |
24 | - | ||
25 | - // checks if the method login of the injected AuthService has been called | ||
26 | expect(AuthServiceMock.login).toHaveBeenCalledWith(credentials); | 31 | expect(AuthServiceMock.login).toHaveBeenCalledWith(credentials); |
27 | - | ||
28 | }); | 32 | }); |
29 | 33 | ||
34 | + it('should open forgot password on click', (done: Function) => { | ||
35 | + spyOn($modal, "open"); | ||
36 | + authController.openForgotPassword(); | ||
37 | + expect($modal.open).toHaveBeenCalled(); | ||
38 | + expect($modal.open).toHaveBeenCalledWith({ | ||
39 | + templateUrl: 'app/login/forgot-password.html', | ||
40 | + controller: AuthController, | ||
41 | + controllerAs: 'vm', | ||
42 | + bindToController: true | ||
43 | + }); | ||
44 | + done(); | ||
45 | + }); | ||
30 | 46 | ||
47 | + it("calls forgotPassword on AuthService when sendPasswdInfo called", done => { | ||
48 | + authController.username = "john"; | ||
49 | + AuthServiceMock.forgotPassword = jasmine.createSpy("forgotPassword").and.returnValue(Promise.resolve()); | ||
50 | + authController.sendPasswdInfo(); | ||
51 | + expect(AuthServiceMock.forgotPassword).toHaveBeenCalledWith("john"); | ||
52 | + done(); | ||
53 | + }); | ||
31 | 54 | ||
32 | }); | 55 | }); |
33 | }); | 56 | }); |
src/app/login/auth.controller.ts
1 | -import {AuthService} from "./auth.service"; | 1 | +import { AuthService } from "./auth.service"; |
2 | +import { NotificationService } from "./../shared/services/notification.service"; | ||
2 | 3 | ||
3 | export class AuthController { | 4 | export class AuthController { |
4 | 5 | ||
5 | - static $inject = ["$log", "$stateParams", "AuthService"]; | 6 | + static $inject = ["$log", "$stateParams", "AuthService", "$uibModal", "NotificationService"]; |
7 | + modalInstance: ng.ui.bootstrap.IModalServiceInstance; | ||
6 | 8 | ||
7 | constructor( | 9 | constructor( |
8 | private $log: ng.ILogService, | 10 | private $log: ng.ILogService, |
9 | private $stateParams: any, | 11 | private $stateParams: any, |
10 | - private AuthService: AuthService | 12 | + private AuthService: AuthService, |
13 | + private $uibModal: ng.ui.bootstrap.IModalService, | ||
14 | + private notificationService: NotificationService | ||
11 | ) { | 15 | ) { |
12 | 16 | ||
13 | } | 17 | } |
14 | 18 | ||
15 | credentials: noosfero.Credentials; | 19 | credentials: noosfero.Credentials; |
20 | + username: string; | ||
16 | 21 | ||
17 | login() { | 22 | login() { |
18 | this.AuthService.login(this.credentials); | 23 | this.AuthService.login(this.credentials); |
19 | } | 24 | } |
25 | + | ||
26 | + openForgotPassword() { | ||
27 | + this.modalInstance = this.$uibModal.open({ | ||
28 | + templateUrl: 'app/login/forgot-password.html', | ||
29 | + controller: AuthController, | ||
30 | + controllerAs: 'vm', | ||
31 | + bindToController: true, | ||
32 | + }); | ||
33 | + } | ||
34 | + | ||
35 | + sendPasswdInfo() { | ||
36 | + this.AuthService.forgotPassword(this.username).then((response) => { | ||
37 | + this.notificationService.info({ title: "forgotPasswd.email_sent.title.info", message: "forgotPasswd.email_sent.message.info" }); | ||
38 | + }).catch((response) => { | ||
39 | + this.notificationService.error({ title: "forgotPasswd.not_found.title.error", message: "forgotPasswd.not_found.message.error" }); | ||
40 | + this.openForgotPassword(); | ||
41 | + }); | ||
42 | + } | ||
20 | } | 43 | } |
src/app/login/auth.service.ts
@@ -2,11 +2,12 @@ import {Injectable, Inject, EventEmitter} from "ng-forward"; | @@ -2,11 +2,12 @@ import {Injectable, Inject, EventEmitter} from "ng-forward"; | ||
2 | 2 | ||
3 | import {NoosferoRootScope, UserResponse} from "./../shared/models/interfaces"; | 3 | import {NoosferoRootScope, UserResponse} from "./../shared/models/interfaces"; |
4 | import {SessionService} from "./session.service"; | 4 | import {SessionService} from "./session.service"; |
5 | +import { RestangularService } from "./../../lib/ng-noosfero-api/http/restangular_service"; | ||
5 | 6 | ||
6 | import {AuthEvents} from "./auth-events"; | 7 | import {AuthEvents} from "./auth-events"; |
7 | 8 | ||
8 | @Injectable() | 9 | @Injectable() |
9 | -@Inject("$http", SessionService, "$log") | 10 | +@Inject("$http", SessionService, "$log", "Restangular") |
10 | export class AuthService { | 11 | export class AuthService { |
11 | 12 | ||
12 | public loginSuccess: EventEmitter<noosfero.User> = new EventEmitter<noosfero.User>(); | 13 | public loginSuccess: EventEmitter<noosfero.User> = new EventEmitter<noosfero.User>(); |
@@ -15,7 +16,10 @@ export class AuthService { | @@ -15,7 +16,10 @@ export class AuthService { | ||
15 | 16 | ||
16 | constructor(private $http: ng.IHttpService, | 17 | constructor(private $http: ng.IHttpService, |
17 | private sessionService: SessionService, | 18 | private sessionService: SessionService, |
18 | - private $log: ng.ILogService) { | 19 | + private $log: ng.ILogService, |
20 | + private Restangular: restangular.IService | ||
21 | + ) { | ||
22 | + this.Restangular = Restangular; | ||
19 | } | 23 | } |
20 | 24 | ||
21 | loginFromCookie() { | 25 | loginFromCookie() { |
@@ -76,4 +80,9 @@ export class AuthService { | @@ -76,4 +80,9 @@ export class AuthService { | ||
76 | throw new Error(`The event: ${eventName} not exists`); | 80 | throw new Error(`The event: ${eventName} not exists`); |
77 | } | 81 | } |
78 | } | 82 | } |
83 | + | ||
84 | + forgotPassword(value: string): ng.IPromise<noosfero.RestResult<any>> { | ||
85 | + return this.Restangular.all("").customPOST("", "forgot_password", {value: value}); | ||
86 | + } | ||
87 | + | ||
79 | } | 88 | } |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +<div class="modal-header"> | ||
2 | + <h3 class="modal-title">{{"auth.form.forgot_passwd" | translate}}</h3> | ||
3 | +</div> | ||
4 | +<div class="modal-body"> | ||
5 | + <form name="forgotPasswdForm"> | ||
6 | + <div class="form-group" ng-class="{'has-error': forgotPasswdForm.username.$error.required }"> | ||
7 | + <label for="forgotPasswdLogin">{{"auth.form.login" | translate}}</label> | ||
8 | + <input type="text" name="username" class="form-control" id="forgotPasswdLogin" placeholder="{{'auth.form.login' | translate}}" ng-model="vm.username" required> | ||
9 | + <div class="help-block" ng-messages="forgotPasswdForm.username.$error"> | ||
10 | + <div ng-if="forgotPasswdForm.username.$touched"> | ||
11 | + <ul class="list-unstyled"> | ||
12 | + <li ng-messages-include="app/shared/messages/form-errors.html"></li> | ||
13 | + </ul> | ||
14 | + </div> | ||
15 | + </div> | ||
16 | + </div> | ||
17 | + <button type="submit" class="btn btn-default btn-block" ng-click="vm.sendPasswdInfo(); $close()" ng-disabled="forgotPasswdForm.$invalid">{{"forgotPasswd.send_instructions_button" | translate}}</button> | ||
18 | + </form> | ||
19 | + <p>{{"forgotPasswd.info" | translate}}</p> | ||
20 | +</div> |
src/app/login/login.html
@@ -18,6 +18,9 @@ | @@ -18,6 +18,9 @@ | ||
18 | {{"auth.form.keepLoggedIn" | translate}} | 18 | {{"auth.form.keepLoggedIn" | translate}} |
19 | </label> | 19 | </label> |
20 | </div> | 20 | </div> |
21 | + <div class="pull-right"> | ||
22 | + <a ng-click="vm.openForgotPassword()" href="">{{"auth.form.forgot_passwd" | translate}}</a> | ||
23 | + </div> | ||
21 | </div> | 24 | </div> |
22 | <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button> | 25 | <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button> |
23 | </form> | 26 | </form> |
src/app/login/login.scss
@@ -0,0 +1,87 @@ | @@ -0,0 +1,87 @@ | ||
1 | +import { ComponentTestHelper, createClass } from "../../spec/component-test-helper"; | ||
2 | +import * as helpers from "../../spec/helpers"; | ||
3 | +import { PasswordComponent } from "./new-password.component"; | ||
4 | + | ||
5 | + | ||
6 | +describe("Password Component", () => { | ||
7 | + const htmlTemplate: string = '<new-password></new-password>'; | ||
8 | + | ||
9 | + let helper: ComponentTestHelper<PasswordComponent>; | ||
10 | + let passwordService = helpers.mocks.passwordService; | ||
11 | + let stateService = jasmine.createSpyObj("$state", ["transitionTo"]); | ||
12 | + let stateParams = jasmine.createSpyObj("$stateParams", ["code"]); | ||
13 | + let notificationService = helpers.mocks.notificationService; | ||
14 | + notificationService.success = jasmine.createSpy('success'); | ||
15 | + notificationService.error = jasmine.createSpy('error'); | ||
16 | + | ||
17 | + | ||
18 | + let data: any; | ||
19 | + | ||
20 | + beforeEach(() => { | ||
21 | + angular.mock.module('templates'); | ||
22 | + angular.mock.module('ngSanitize'); | ||
23 | + angular.mock.module('ngMessages'); | ||
24 | + angular.mock.module('ngPassword'); | ||
25 | + }); | ||
26 | + | ||
27 | + beforeEach((done) => { | ||
28 | + let cls = createClass({ | ||
29 | + template: htmlTemplate, | ||
30 | + directives: [PasswordComponent], | ||
31 | + providers: [ | ||
32 | + helpers.createProviderToValue('$state', stateService), | ||
33 | + helpers.createProviderToValue('$stateParams', stateParams), | ||
34 | + helpers.createProviderToValue('PasswordService', passwordService), | ||
35 | + helpers.createProviderToValue('NotificationService', notificationService), | ||
36 | + ] | ||
37 | + }); | ||
38 | + helper = new ComponentTestHelper<PasswordComponent>(cls, done); | ||
39 | + }); | ||
40 | + | ||
41 | + it('new password page was rendered', () => { | ||
42 | + expect(helper.debugElement.query('div.new-password-page').length).toEqual(1); | ||
43 | + }); | ||
44 | + | ||
45 | + it("changes the user password", done => { | ||
46 | + data = { | ||
47 | + code: '1234567890', | ||
48 | + password: 'test', | ||
49 | + passwordConfirmation: 'test' | ||
50 | + }; | ||
51 | + | ||
52 | + helper.component.code = data.code; | ||
53 | + helper.component.password = data.password; | ||
54 | + helper.component.passwordConfirmation = data.passwordConfirmation; | ||
55 | + | ||
56 | + passwordService.new_password = jasmine.createSpy("new_password").and.returnValue(Promise.resolve()); | ||
57 | + | ||
58 | + helper.component.sendNewPassword(); | ||
59 | + expect(passwordService.new_password).toHaveBeenCalledWith('1234567890', 'test', 'test'); | ||
60 | + | ||
61 | + expect(notificationService.success).toHaveBeenCalled(); | ||
62 | + | ||
63 | + done(); | ||
64 | + }); | ||
65 | + | ||
66 | + it("fails when try to change the user password", done => { | ||
67 | + data = { | ||
68 | + code: '1234567890', | ||
69 | + password: 'test', | ||
70 | + passwordConfirmation: 'test-invalid' | ||
71 | + }; | ||
72 | + | ||
73 | + helper.component.code = data.code; | ||
74 | + helper.component.password = data.password; | ||
75 | + helper.component.passwordConfirmation = data.passwordConfirmation; | ||
76 | + | ||
77 | + passwordService.new_password = jasmine.createSpy("new_password").and.returnValue(Promise.reject({data: {message: 'Error'}})); | ||
78 | + | ||
79 | + helper.component.sendNewPassword(); | ||
80 | + expect(passwordService.new_password).toHaveBeenCalledWith('1234567890', 'test', 'test-invalid'); | ||
81 | + | ||
82 | + expect(notificationService.error).toHaveBeenCalled(); | ||
83 | + | ||
84 | + done(); | ||
85 | + }); | ||
86 | + | ||
87 | +}); |
@@ -0,0 +1,36 @@ | @@ -0,0 +1,36 @@ | ||
1 | +import { StateConfig, Component, Inject, provide } from 'ng-forward'; | ||
2 | + | ||
3 | +import { PasswordService } from "../../lib/ng-noosfero-api/http/password.service"; | ||
4 | +import { NotificationService } from "./../shared/services/notification.service"; | ||
5 | +import { AuthController } from "./auth.controller"; | ||
6 | + | ||
7 | +@Component({ | ||
8 | + selector: 'new-password', | ||
9 | + templateUrl: 'app/login/new-password.html', | ||
10 | + providers: [provide('passwordService', { useClass: PasswordService })] | ||
11 | +}) | ||
12 | +@Inject(PasswordService, "$state", "$stateParams", NotificationService) | ||
13 | +export class PasswordComponent { | ||
14 | + | ||
15 | + code: string; | ||
16 | + password: string; | ||
17 | + passwordConfirmation: string; | ||
18 | + | ||
19 | + constructor( | ||
20 | + public passwordService: PasswordService, | ||
21 | + private $state: ng.ui.IStateService, | ||
22 | + private $stateParams: ng.ui.IStateParamsService, | ||
23 | + private notificationService: NotificationService) { | ||
24 | + | ||
25 | + this.code = this.$stateParams['code']; | ||
26 | + } | ||
27 | + | ||
28 | + sendNewPassword() { | ||
29 | + this.passwordService.new_password(this.code, this.password, this.passwordConfirmation).then((response) => { | ||
30 | + this.notificationService.success({ title: "newPasswd.success.title", message: "newPasswd.success.message", timer: 5000 }); | ||
31 | + this.$state.transitionTo('main.environment'); | ||
32 | + }).catch((response) => { | ||
33 | + this.notificationService.error({ title: "newPasswd.failed.title", message: "newPasswd.failed.message" }); | ||
34 | + }); | ||
35 | + } | ||
36 | +} |
@@ -0,0 +1,31 @@ | @@ -0,0 +1,31 @@ | ||
1 | +<div class="new-password-page col-xs-4 col-xs-offset-4"> | ||
2 | + <h1>{{"new_password.welcomeMessageTitle" | translate }}</h1> | ||
3 | + | ||
4 | + <form name="newPasswdForm"> | ||
5 | + <div class="form-group" ng-class="{'has-error': newPasswdForm.password.$invalid && newPasswdForm.password.$touched }"> | ||
6 | + <div class="input-group"> | ||
7 | + <span class="input-group-addon"><i class="fa fa-lock"></i></span> | ||
8 | + <input type="password" class="form-control" id="password" name="password" placeholder="{{'account.register.passwordLabel' | translate }}" ng-model="vm.password" required minlength="4"> | ||
9 | + </div> | ||
10 | + <div class="help-block" ng-show="newPasswdForm.password.$touched" ng-messages="newPasswdForm.password.$error"> | ||
11 | + <ul class="list-unstyled"> | ||
12 | + <li ng-messages-include="app/shared/messages/form-errors.html"></li> | ||
13 | + </ul> | ||
14 | + </div> | ||
15 | + </div> | ||
16 | + | ||
17 | + <div class="form-group" ng-class="{'has-error': newPasswdForm.passwordConfirm.$invalid && newPasswdForm.passwordConfirm.$touched }"> | ||
18 | + <div class="input-group"> | ||
19 | + <span class="input-group-addon"><i class="fa fa-unlock-alt"></i></span> | ||
20 | + <input type="password" class="form-control" id="passwordConfirm" name="passwordConfirm" match-password="password" placeholder="{{'account.register.passwordConfirmationLabel' | translate}}" ng-model="vm.passwordConfirmation" required> | ||
21 | + </div> | ||
22 | + <div class="help-block" ng-show="newPasswdForm.passwordConfirm.$touched" ng-messages="newPasswdForm.passwordConfirm.$error"> | ||
23 | + <ul class="list-unstyled"> | ||
24 | + <li ng-messages-include="app/shared/messages/form-errors.html"></li> | ||
25 | + <li ng-message="passwordMatch" translate="messages.invalid.passwordMatch"></li> | ||
26 | + </ul> | ||
27 | + </div> | ||
28 | + </div> | ||
29 | + <button type="submit" class="btn btn-default btn-block" ng-click="vm.sendNewPassword()" ng-disabled="newPasswdForm.$invalid">{{"newPasswdForm.submit" | translate}}</button> | ||
30 | + </form> | ||
31 | +</div> |
src/app/main/main.component.ts
@@ -4,6 +4,7 @@ import { ArticleBlogComponent } from "./../article/types/blog/blog.component"; | @@ -4,6 +4,7 @@ import { ArticleBlogComponent } from "./../article/types/blog/blog.component"; | ||
4 | 4 | ||
5 | import { ArticleViewComponent } from "./../article/article-default-view.component"; | 5 | import { ArticleViewComponent } from "./../article/article-default-view.component"; |
6 | 6 | ||
7 | +import { PasswordComponent } from "../login/new-password.component"; | ||
7 | import { ProfileComponent } from "../profile/profile.component"; | 8 | import { ProfileComponent } from "../profile/profile.component"; |
8 | import { BoxesComponent } from "../layout/boxes/boxes.component"; | 9 | import { BoxesComponent } from "../layout/boxes/boxes.component"; |
9 | import { BlockContentComponent } from "../layout/blocks/block-content.component"; | 10 | import { BlockContentComponent } from "../layout/blocks/block-content.component"; |
@@ -120,7 +121,8 @@ export class EnvironmentContent { | @@ -120,7 +121,8 @@ export class EnvironmentContent { | ||
120 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, | 121 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, |
121 | LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, | 122 | LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, |
122 | PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, | 123 | PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, |
123 | - ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent | 124 | + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent, |
125 | + PasswordComponent | ||
124 | ].concat(plugins.mainComponents).concat(plugins.hotspots), | 126 | ].concat(plugins.mainComponents).concat(plugins.hotspots), |
125 | providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, | 127 | providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, |
126 | "ngAnimate", "ngCookies", "ngStorage", "ngTouch", | 128 | "ngAnimate", "ngCookies", "ngStorage", "ngTouch", |
@@ -172,6 +174,18 @@ export class EnvironmentContent { | @@ -172,6 +174,18 @@ export class EnvironmentContent { | ||
172 | } | 174 | } |
173 | }, | 175 | }, |
174 | { | 176 | { |
177 | + url: "/account/new_password/:code", | ||
178 | + component: PasswordComponent, | ||
179 | + name: 'main.newPasswd', | ||
180 | + views: { | ||
181 | + "content": { | ||
182 | + templateUrl: "app/login/new-password.html", | ||
183 | + controller: PasswordComponent, | ||
184 | + controllerAs: "vm" | ||
185 | + } | ||
186 | + } | ||
187 | + }, | ||
188 | + { | ||
175 | url: "^/:profile", | 189 | url: "^/:profile", |
176 | abstract: true, | 190 | abstract: true, |
177 | component: ProfileComponent, | 191 | component: ProfileComponent, |
src/app/shared/services/notification.service.ts
@@ -14,6 +14,8 @@ export class NotificationService { | @@ -14,6 +14,8 @@ export class NotificationService { | ||
14 | public static DEFAULT_ERROR_TITLE = "notification.error.default.title"; | 14 | public static DEFAULT_ERROR_TITLE = "notification.error.default.title"; |
15 | public static DEFAULT_ERROR_MESSAGE = "notification.error.default.message"; | 15 | public static DEFAULT_ERROR_MESSAGE = "notification.error.default.message"; |
16 | public static DEFAULT_SUCCESS_TIMER = 1000; | 16 | public static DEFAULT_SUCCESS_TIMER = 1000; |
17 | + public static DEFAULT_INFO_TITLE = "notification.info.default.title"; | ||
18 | + public static DEFAULT_INFO_MESSAGE = "notification.info.default.message"; | ||
17 | 19 | ||
18 | error({ | 20 | error({ |
19 | message = NotificationService.DEFAULT_ERROR_MESSAGE, | 21 | message = NotificationService.DEFAULT_ERROR_MESSAGE, |
@@ -40,6 +42,14 @@ export class NotificationService { | @@ -40,6 +42,14 @@ export class NotificationService { | ||
40 | this.showMessage({ title: title, text: message, showCancelButton: showCancelButton, type: type, closeOnConfirm: false }, confirmationFunction); | 42 | this.showMessage({ title: title, text: message, showCancelButton: showCancelButton, type: type, closeOnConfirm: false }, confirmationFunction); |
41 | } | 43 | } |
42 | 44 | ||
45 | + info({ | ||
46 | + message = NotificationService.DEFAULT_INFO_MESSAGE, | ||
47 | + title = NotificationService.DEFAULT_INFO_TITLE, | ||
48 | + showConfirmButton = true | ||
49 | + } = {}) { | ||
50 | + this.showMessage({ title: title, text: message, showConfirmButton: showConfirmButton, type: "info" }); | ||
51 | + } | ||
52 | + | ||
43 | private showMessage({title, text, type = "success", timer = null, showConfirmButton = true, showCancelButton = false, closeOnConfirm = true}, confirmationFunction: Function = null) { | 53 | private showMessage({title, text, type = "success", timer = null, showConfirmButton = true, showCancelButton = false, closeOnConfirm = true}, confirmationFunction: Function = null) { |
44 | this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage()); | 54 | this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage()); |
45 | this.SweetAlert.swal({ | 55 | this.SweetAlert.swal({ |
src/languages/en.json
@@ -34,8 +34,15 @@ | @@ -34,8 +34,15 @@ | ||
34 | "auth.form.login": "Username or Email address", | 34 | "auth.form.login": "Username or Email address", |
35 | "auth.form.password": "Password", | 35 | "auth.form.password": "Password", |
36 | "auth.form.keepLoggedIn": "Keep me logged in", | 36 | "auth.form.keepLoggedIn": "Keep me logged in", |
37 | + "auth.form.forgot_passwd": "Forgot your password?", | ||
37 | "auth.form.login_button": "Login", | 38 | "auth.form.login_button": "Login", |
38 | "auth.createAccount": "Create account", | 39 | "auth.createAccount": "Create account", |
40 | + "forgotPasswd.send_instructions_button": "Send instructions", | ||
41 | + "forgotPasswd.info": "Upon clicking on the button above, you'll receive an email with instructions on how to create a new password.", | ||
42 | + "forgotPasswd.email_sent.title.info": "Change your password", | ||
43 | + "forgotPasswd.email_sent.message.info": "Follow the instructions sent by email to change your password", | ||
44 | + "forgotPasswd.not_found.title.error": "User not found", | ||
45 | + "forgotPasswd.not_found.message.error": "Could not send password recovery for the user", | ||
39 | "navbar.content_viewer_actions.new_item": "New Item", | 46 | "navbar.content_viewer_actions.new_item": "New Item", |
40 | "navbar.profile_actions.new_item": "New Item", | 47 | "navbar.profile_actions.new_item": "New Item", |
41 | "navbar.content_viewer_actions.new_post": "New Post", | 48 | "navbar.content_viewer_actions.new_post": "New Post", |
@@ -43,6 +50,8 @@ | @@ -43,6 +50,8 @@ | ||
43 | "navbar.profile_actions.new_discussion": "New Discussion", | 50 | "navbar.profile_actions.new_discussion": "New Discussion", |
44 | "notification.error.default.message": "Something went wrong!", | 51 | "notification.error.default.message": "Something went wrong!", |
45 | "notification.error.default.title": "Oops...", | 52 | "notification.error.default.title": "Oops...", |
53 | + "notification.info.default.message": "", | ||
54 | + "notification.info.default.title": "Information", | ||
46 | "notification.profile.not_found": "Page not found", | 55 | "notification.profile.not_found": "Page not found", |
47 | "notification.http_error.401.message": "Unauthorized", | 56 | "notification.http_error.401.message": "Unauthorized", |
48 | "notification.http_error.500.message": "Server error", | 57 | "notification.http_error.500.message": "Server error", |
src/languages/pt.json
@@ -34,8 +34,15 @@ | @@ -34,8 +34,15 @@ | ||
34 | "auth.form.login": "Nome de usuário ou Email", | 34 | "auth.form.login": "Nome de usuário ou Email", |
35 | "auth.form.password": "Senha", | 35 | "auth.form.password": "Senha", |
36 | "auth.form.keepLoggedIn": "Continuar logado", | 36 | "auth.form.keepLoggedIn": "Continuar logado", |
37 | + "auth.form.forgot_passwd": "Esqueceu sua senha?", | ||
37 | "auth.form.login_button": "Login", | 38 | "auth.form.login_button": "Login", |
38 | "auth.createAccount": "Criar conta", | 39 | "auth.createAccount": "Criar conta", |
40 | + "forgotPasswd.send_instructions_button": "Send instructions", | ||
41 | + "forgotPasswd.info": "Ao clicar no botão acima, você receberá um email com instruções para criar uma nova senha.", | ||
42 | + "forgotPasswd.email_sent.title.info": "Altere sua senha", | ||
43 | + "forgotPasswd.email_sent.message.info": "Siga as instruções enviadas por e-mail para alterar sua senha", | ||
44 | + "forgotPasswd.not_found.title.error": "Usuário não encontrado", | ||
45 | + "forgotPasswd.not_found.message.error": "Não foi possível recuperar a senha deste usuário", | ||
39 | "navbar.content_viewer_actions.new_item": "Novo Item", | 46 | "navbar.content_viewer_actions.new_item": "Novo Item", |
40 | "navbar.profile_actions.new_item": "Novo Item", | 47 | "navbar.profile_actions.new_item": "Novo Item", |
41 | "navbar.content_viewer_actions.new_post": "Novo Post", | 48 | "navbar.content_viewer_actions.new_post": "Novo Post", |
@@ -43,6 +50,8 @@ | @@ -43,6 +50,8 @@ | ||
43 | "navbar.profile_actions.new_discussion": "Nova Discussão", | 50 | "navbar.profile_actions.new_discussion": "Nova Discussão", |
44 | "notification.error.default.message": "Algo deu errado!", | 51 | "notification.error.default.message": "Algo deu errado!", |
45 | "notification.error.default.title": "Oops...", | 52 | "notification.error.default.title": "Oops...", |
53 | + "notification.info.default.message": "", | ||
54 | + "notification.info.default.title": "Informação", | ||
46 | "notification.profile.not_found": "Página não encontrada", | 55 | "notification.profile.not_found": "Página não encontrada", |
47 | "notification.http_error.401.message": "Não autorizado", | 56 | "notification.http_error.401.message": "Não autorizado", |
48 | "notification.http_error.500.message": "Erro no servidor", | 57 | "notification.http_error.500.message": "Erro no servidor", |
src/lib/ng-noosfero-api/http/password.service.spec.ts
0 → 100644
@@ -0,0 +1,41 @@ | @@ -0,0 +1,41 @@ | ||
1 | +import { PasswordService } from "./password.service"; | ||
2 | + | ||
3 | +describe("Services", () => { | ||
4 | + | ||
5 | + describe("Password Service", () => { | ||
6 | + | ||
7 | + let $httpBackend: ng.IHttpBackendService; | ||
8 | + let passwordService: PasswordService; | ||
9 | + let $rootScope: ng.IRootScopeService; | ||
10 | + let data: any; | ||
11 | + | ||
12 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | ||
13 | + $translateProvider.translations('en', {}); | ||
14 | + })); | ||
15 | + | ||
16 | + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, | ||
17 | +_PasswordService_: PasswordService, _$rootScope_: ng.IRootScopeService) => { | ||
18 | + $httpBackend = _$httpBackend_; | ||
19 | + passwordService = _PasswordService_; | ||
20 | + $rootScope = _$rootScope_; | ||
21 | + })); | ||
22 | + | ||
23 | + describe("Succesfull request", () => { | ||
24 | + | ||
25 | + it("should change user password", (done) => { | ||
26 | + data = { | ||
27 | + code: '1234567890', | ||
28 | + password: 'test', | ||
29 | + password_confirmation: 'test' | ||
30 | + }; | ||
31 | + | ||
32 | + $httpBackend.expectPATCH(`/api/v1/new_password?code=${data.code}&password=${data.password}&password_confirmation=${data.password_confirmation}`).respond(201, [{ login: "test" }]); | ||
33 | + passwordService.new_password('1234567890', 'test', 'test').then((response: restangular.IResponse) => { | ||
34 | + expect(response.data[0].login).toEqual("test"); | ||
35 | + done(); | ||
36 | + }); | ||
37 | + $httpBackend.flush(); | ||
38 | + }); | ||
39 | + }); | ||
40 | + }); | ||
41 | +}); |
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +import { Injectable, Inject } from "ng-forward"; | ||
2 | +import { RestangularService } from "./restangular_service"; | ||
3 | + | ||
4 | +@Injectable() | ||
5 | +@Inject("Restangular") | ||
6 | +export class PasswordService { | ||
7 | + constructor(private Restangular: restangular.IService) { | ||
8 | + this.Restangular = Restangular; | ||
9 | + } | ||
10 | + | ||
11 | + new_password(code: string, password: string, password_confirmation: string): ng.IPromise<noosfero.RestResult<noosfero.User>> { | ||
12 | + return this.Restangular.all("").customOperation("patch", "new_password", {code: code, password: password, password_confirmation: password_confirmation }); | ||
13 | + } | ||
14 | +} |
src/spec/mocks.ts
@@ -257,6 +257,11 @@ export var mocks: any = { | @@ -257,6 +257,11 @@ export var mocks: any = { | ||
257 | changeLanguage: (lang: string) => { }, | 257 | changeLanguage: (lang: string) => { }, |
258 | translate: (text: string) => { return text; } | 258 | translate: (text: string) => { return text; } |
259 | }, | 259 | }, |
260 | + passwordService: { | ||
261 | + new_password: (param: any) => { | ||
262 | + return Promise.resolve({ status: 201 }); | ||
263 | + } | ||
264 | + }, | ||
260 | notificationService: { | 265 | notificationService: { |
261 | success: () => { }, | 266 | success: () => { }, |
262 | confirmation: () => { }, | 267 | confirmation: () => { }, |
-
Added 27 new commits:
- 29bd5738 - Added new 'per_page' to SearchComponent
- a31408fa - Added sass variable to search result green color
- 11aaae15 - Added 'per_page' param into @StateConfig environment router definition
- 87126262 - Merge branch 'search-ui' into 'master'
- 4d167676 - Add component to display tasks in menu
- 917544be - Add component to list tasks
- 07f15849 - Add accept/reject buttons to task list
- 45183e9d - Add tests to task components
- 118eeb9d - List roles when accept add member tasks
- 0354c769 - Accept parameters when accept/reject tasks
- 7205a81d - Emit and subscribe to events for task accept/reject
- 917a672d - Translate tasks messages
- 1adb5479 - Use .checkbox-nice in accept member task
- 6fca41b5 - Refactor methods to close task
- d6d5bae0 - Create interface for AddMemberTask
- a1375e04 - Merge branch 'tasks' into 'master'
- 07858dff - Initial implementation
- ccf7dc63 - Added upload image to person service
- c132a4a9 - Upload image initial and tests
- 338aae2e - Minor refactory and sintax adjusts
- 71d22bf6 - Merge branch 'upload-profile-photo' into 'master'
- b8cf9be2 - Removed console.log() calls from Upload image related files
- ce2f9d52 - Set the browser lang like default translate language
- d06a730a - Merge branch 'translate-browser-lang' into 'master'
- afb4744a - Do not show join/hide button for person
- 33647b98 - Ticket #107 Implementation of 'Forgot password'
- a05a7c31 - Ticket #107 Implementation of new password generation
-
incorporado manualmente
-
Status changed to closed