Merge Request #59

Closed
noosfero-themes/angular-theme!59
Created by Daniela Feitosa

Forgot password

Assignee: Caio Almeida
Milestone: None

Closed by Leandro Santos

Changes were not merged into target branch

Commits (2)
4 participants
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 }
src/app/login/forgot-password.html 0 → 100644
@@ -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
@@ -10,6 +10,12 @@ @@ -10,6 +10,12 @@
10 margin-top: 20px; 10 margin-top: 20px;
11 text-transform: uppercase; 11 text-transform: uppercase;
12 } 12 }
  13 + .form-inline {
  14 + display: inline;
  15 + div {
  16 + display: inline-block;
  17 + }
  18 + }
13 } 19 }
14 } 20 }
15 21
src/app/login/new-password.component.spec.ts 0 → 100644
@@ -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 +});
src/app/login/new-password.component.ts 0 → 100644
@@ -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 +}
src/app/login/new-password.html 0 → 100644
@@ -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 &quot;./../article/types/blog/blog.component&quot;; @@ -4,6 +4,7 @@ import { ArticleBlogComponent } from &quot;./../article/types/blog/blog.component&quot;;
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 +});
src/lib/ng-noosfero-api/http/password.service.ts 0 → 100644
@@ -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: () => { },
  • 0743c2eb05e68c92baa5f8c498995a20?s=40&d=identicon
    Daniela Feitosa @daniela

    Reassigned to @caiosba

    Choose File ...   File name...
    Cancel
  • C8b72d0556872a2aea21e8fed0a72001?s=40&d=identicon
    Melissa Wen @melissawen

    mentioned in issue #107

    Choose File ...   File name...
    Cancel
  • 0743c2eb05e68c92baa5f8c498995a20?s=40&d=identicon
    Daniela Feitosa @daniela

    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
    Choose File ...   File name...
    Cancel
  • 5bf9bf341e9d00ebd854cdaf1a4299b2?s=40&d=identicon
    Leandro Santos @leandronunes

    incorporado manualmente

    Choose File ...   File name...
    Cancel
  • 5bf9bf341e9d00ebd854cdaf1a4299b2?s=40&d=identicon
    Leandro Santos @leandronunes

    Status changed to closed

    Choose File ...   File name...
    Cancel