diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee
index f38665a4..60825531 100644
--- a/app/coffee/modules/resources.coffee
+++ b/app/coffee/modules/resources.coffee
@@ -31,6 +31,7 @@ urls = {
"roles": "/api/v1/roles"
"projects": "/api/v1/projects"
"memberships": "/api/v1/memberships"
+ "notify-policies": "/api/v1/notify-policies"
"bulk-create-memberships": "/api/v1/memberships/bulk_create"
"milestones": "/api/v1/milestones"
"userstories": "/api/v1/userstories"
@@ -69,6 +70,7 @@ urls = {
"users-password-recovery": "/api/v1/users/password_recovery"
"users-change-password-from-recovery": "/api/v1/users/change_password_from_recovery"
"users-change-password": "/api/v1/users/change_password"
+ "users-change-email": "/api/v1/users/change_email"
"resolver": "/api/v1/resolver"
"userstory-statuses": "/api/v1/userstory-statuses"
"points": "/api/v1/points"
@@ -115,8 +117,10 @@ module.run([
"$tgResources",
"$tgProjectsResourcesProvider",
"$tgMembershipsResourcesProvider",
+ "$tgNotifyPoliciesResourcesProvider",
"$tgInvitationsResourcesProvider",
"$tgRolesResourcesProvider",
+ "$tgUserSettingsResourcesProvider",
"$tgSprintsResourcesProvider",
"$tgUserstoriesResourcesProvider",
"$tgTasksResourcesProvider",
diff --git a/app/coffee/modules/resources/memberships.coffee b/app/coffee/modules/resources/memberships.coffee
index dd816523..de569993 100644
--- a/app/coffee/modules/resources/memberships.coffee
+++ b/app/coffee/modules/resources/memberships.coffee
@@ -33,6 +33,11 @@ resourceProvider = ($repo, $http, $urls) ->
params = _.extend({}, params, filters or {})
return $repo.queryPaginated("memberships", params)
+ service.listByUser = (userId, filters) ->
+ params = {user: userId}
+ params = _.extend({}, params, filters or {})
+ return $repo.queryPaginated("memberships", params)
+
service.resendInvitation = (id) ->
url = $urls.resolve("memberships")
return $http.post("#{url}/#{id}/resend_invitation", {})
diff --git a/app/coffee/modules/resources/notify-policies.coffee b/app/coffee/modules/resources/notify-policies.coffee
new file mode 100644
index 00000000..654bc5db
--- /dev/null
+++ b/app/coffee/modules/resources/notify-policies.coffee
@@ -0,0 +1,40 @@
+###
+# Copyright (C) 2014 Andrey Antukh
+# Copyright (C) 2014 Jesús Espino Garcia
+# Copyright (C) 2014 David Barragán Merino
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# File: modules/resources/memberships.coffee
+###
+
+
+taiga = @.taiga
+
+resourceProvider = ($repo, $http, $urls) ->
+ service = {}
+
+ service.get = (id) ->
+ return $repo.queryOne("notify-policies", id)
+
+ service.list = (filters) ->
+ params = _.extend({}, params, filters or {})
+ return $repo.queryMany("notify-policies", params)
+
+ return (instance) ->
+ instance.notifyPolicies = service
+
+
+module = angular.module("taigaResources")
+module.factory("$tgNotifyPoliciesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])
diff --git a/app/coffee/modules/resources/user-settings.coffee b/app/coffee/modules/resources/user-settings.coffee
new file mode 100644
index 00000000..c9bfe0b5
--- /dev/null
+++ b/app/coffee/modules/resources/user-settings.coffee
@@ -0,0 +1,55 @@
+###
+# Copyright (C) 2014 Andrey Antukh
+# Copyright (C) 2014 Jesús Espino Garcia
+# Copyright (C) 2014 David Barragán Merino
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# File: modules/resources/memberships.coffee
+###
+
+
+taiga = @.taiga
+
+resourceProvider = ($repo, $http, $urls) ->
+ service = {}
+
+ service.changeAvatar = (attachmentModel) ->
+ data = new FormData()
+ data.append('avatar', attachmentModel)
+ options = {
+ transformRequest: angular.identity,
+ headers: {'Content-Type': undefined}
+ }
+ url = "#{$urls.resolve("users")}/change_avatar"
+ return $http.post(url, data, {}, options)
+
+ service.removeAvatar = () ->
+ url = "#{$urls.resolve("users")}/remove_avatar"
+ return $http.post(url)
+
+ service.changePassword = (currentPassword, newPassword) ->
+ url = "#{$urls.resolve("users")}/change_password"
+ data = {
+ current_password: currentPassword
+ password: newPassword
+ }
+ return $http.post(url, data)
+
+ return (instance) ->
+ instance.userSettings = service
+
+
+module = angular.module("taigaResources")
+module.factory("$tgUserSettingsResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", resourceProvider])
diff --git a/app/coffee/modules/user-settings/change-password.coffee b/app/coffee/modules/user-settings/change-password.coffee
new file mode 100644
index 00000000..799c236c
--- /dev/null
+++ b/app/coffee/modules/user-settings/change-password.coffee
@@ -0,0 +1,93 @@
+###
+# Copyright (C) 2014 Andrey Antukh
+# Copyright (C) 2014 Jesús Espino Garcia
+# Copyright (C) 2014 David Barragán Merino
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# File: modules/user-settings/main.coffee
+###
+
+taiga = @.taiga
+
+mixOf = @.taiga.mixOf
+
+module = angular.module("taigaUserSettings")
+
+
+#############################################################################
+## User ChangePassword Controller
+#############################################################################
+
+class UserChangePasswordController extends mixOf(taiga.Controller, taiga.PageMixin)
+ @.$inject = [
+ "$scope",
+ "$rootScope",
+ "$tgRepo",
+ "$tgConfirm",
+ "$tgResources",
+ "$routeParams",
+ "$q",
+ "$location",
+ "$tgAuth"
+ ]
+
+ constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @auth) ->
+ @scope.sectionName = "Change Password" #i18n
+ @scope.project = {}
+ @scope.user = @auth.getUser()
+
+ promise = @.loadInitialData()
+ promise.then null, ->
+ console.log "FAIL" #TODO
+
+ loadProject: ->
+ return @rs.projects.get(@scope.projectId).then (project) =>
+ @scope.project = project
+ return project
+
+ loadInitialData: ->
+ promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
+ @scope.projectId = data.project
+ return data
+
+ return promise.then(=> @.loadProject())
+
+ save: ->
+ if @scope.newPassword1 != @scope.newPassword2
+ @confirm.notify('error', "The passwords dosn't match")
+ return
+
+ promise = @rs.userSettings.changePassword(@scope.currentPassword, @scope.newPassword1)
+ promise.then =>
+ @confirm.notify('success')
+ promise.then null, (response) =>
+ @confirm.notify('error', response.data._error_message)
+
+
+module.controller("UserChangePasswordController", UserChangePasswordController)
+
+#############################################################################
+## User ChangePassword Directive
+#############################################################################
+
+UserChangePasswordDirective = () ->
+ link = ($scope, $el, $attrs) ->
+
+ $scope.$on "$destroy", ->
+ $el.off()
+
+ return {link:link}
+
+module.directive("tgUserChangePassword", UserChangePasswordDirective)
diff --git a/app/coffee/modules/user-settings/lightboxes.coffee b/app/coffee/modules/user-settings/lightboxes.coffee
new file mode 100644
index 00000000..a1343fd3
--- /dev/null
+++ b/app/coffee/modules/user-settings/lightboxes.coffee
@@ -0,0 +1,66 @@
+###
+# Copyright (C) 2014 Andrey Antukh
+# Copyright (C) 2014 Jesús Espino Garcia
+# Copyright (C) 2014 David Barragán Merino
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# File: modules/issues/lightboxes.coffee
+###
+
+taiga = @.taiga
+bindOnce = @.taiga.bindOnce
+
+module = angular.module("taigaUserSettings")
+
+#############################################################################
+## Delete User Lightbox Directive
+#############################################################################
+
+DeleteUserDirective = ($repo, $rootscope, $auth, $location) ->
+ link = ($scope, $el, $attrs) ->
+ $scope.$on "deletelightbox:new", (ctx, user)->
+ $el.removeClass("hidden")
+
+ $scope.$on "$destroy", ->
+ $el.off()
+
+ submit = ->
+ promise = $repo.remove($scope.user)
+
+ promise.then (data) ->
+ $el.addClass("hidden")
+ $auth.logout()
+ $location.path("/login")
+
+ # FIXME: error handling?
+ promise.then null, ->
+ console.log "FAIL"
+
+ $el.on "click", ".close", (event) ->
+ event.preventDefault()
+ $el.addClass("hidden")
+
+ $el.on "click", ".button-red", (event) ->
+ event.preventDefault()
+ $el.addClass("hidden")
+
+ $el.on "click", ".button-green", (event) ->
+ event.preventDefault()
+ submit()
+
+ return {link:link}
+
+
+module.directive("tgLbDeleteUser", ["$tgRepo", "$rootScope", "$tgAuth", "$location", DeleteUserDirective])
diff --git a/app/coffee/modules/user-settings/main.coffee b/app/coffee/modules/user-settings/main.coffee
index b6c8b527..2f1794d7 100644
--- a/app/coffee/modules/user-settings/main.coffee
+++ b/app/coffee/modules/user-settings/main.coffee
@@ -65,6 +65,27 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin)
return promise.then(=> @.loadProject())
+ saveUserProfile: ->
+
+ updatingEmail = @scope.user.isAttributeModified("email")
+ promise = @repo.save(@scope.user)
+ promise.then =>
+ @auth.setUser(@scope.user)
+ if updatingEmail
+ @confirm.success("Check your inbox!
+ We have sent a mail to your account
+ with the instructions to set your new address") #TODO: i18n
+
+ else
+ @confirm.notify('success')
+
+ promise.then null, (response) =>
+ @confirm.notify('error', response._error_message)
+ @scope.user = @auth.getUser()
+
+ openDeleteLightbox: ->
+ @rootscope.$broadcast("deletelightbox:new", @scope.user)
+
module.controller("UserSettingsController", UserSettingsController)
@@ -74,6 +95,13 @@ module.controller("UserSettingsController", UserSettingsController)
UserProfileDirective = () ->
link = ($scope, $el, $attrs) ->
+ form = $el.find("form").checksley()
+
+ $el.on "click", ".user-profile form .save-profile", (event) ->
+ return if not form.validate()
+ target = angular.element(event.currentTarget)
+ $ctrl = $el.controller()
+ $ctrl.saveUserProfile()
$scope.$on "$destroy", ->
$el.off()
@@ -81,3 +109,52 @@ UserProfileDirective = () ->
return {link:link}
module.directive("tgUserProfile", UserProfileDirective)
+
+
+#############################################################################
+## User Avatar Directive
+#############################################################################
+
+UserAvatarDirective = ($auth, $model, $rs, $confirm) ->
+ link = ($scope, $el, $attrs) ->
+
+ $scope.$on "$destroy", ->
+ $el.off()
+
+ $el.on "click", ".button.change", ->
+ $el.find("#avatar-field").click()
+
+ $el.on "change", "#avatar-field", (event) ->
+ target = angular.element(event.currentTarget)
+
+ promise = $rs.userSettings.changeAvatar($scope.avatarAttachment)
+
+ promise.then (response) ->
+ user = $model.make_model("users", response.data)
+ $auth.setUser(user)
+ $scope.user = user
+ $confirm.notify('success')
+ promise.then null, (response) ->
+ console.log response
+ $confirm.notify('error', response.data._error_message)
+
+ return {link:link}
+
+module.directive("tgUserAvatar", ["$tgAuth", "$tgModel", "$tgResources", "$tgConfirm", UserAvatarDirective])
+
+#############################################################################
+## User Avatar Model Directive
+#############################################################################
+
+TaigaAvatarModelDirective = ($parse) ->
+ link = ($scope, $el, $attrs) ->
+ model = $parse($attrs.tgAvatarModel)
+ modelSetter = model.assign
+
+ $el.bind 'change', ->
+ $scope.$apply ->
+ modelSetter($scope, $el[0].files[0])
+
+ return {link:link}
+
+module.directive('tgAvatarModel', ['$parse', TaigaAvatarModelDirective])
diff --git a/app/coffee/modules/user-settings/notifications.coffee b/app/coffee/modules/user-settings/notifications.coffee
new file mode 100644
index 00000000..97d61235
--- /dev/null
+++ b/app/coffee/modules/user-settings/notifications.coffee
@@ -0,0 +1,153 @@
+###
+# Copyright (C) 2014 Andrey Antukh
+# Copyright (C) 2014 Jesús Espino Garcia
+# Copyright (C) 2014 David Barragán Merino
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# File: modules/user-settings/main.coffee
+###
+
+taiga = @.taiga
+
+mixOf = @.taiga.mixOf
+bindOnce = @.taiga.bindOnce
+
+module = angular.module("taigaUserSettings")
+
+
+#############################################################################
+## User settings Controller
+#############################################################################
+
+class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixin)
+ @.$inject = [
+ "$scope",
+ "$rootScope",
+ "$tgRepo",
+ "$tgConfirm",
+ "$tgResources",
+ "$routeParams",
+ "$q",
+ "$location",
+ "$tgAuth"
+ ]
+
+ constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @auth) ->
+ @scope.sectionName = "Email Notifications" #i18n
+ @scope.project = {}
+ @scope.user = @auth.getUser()
+
+ promise = @.loadInitialData()
+ promise.then null, ->
+ console.log "FAIL" #TODO
+
+ loadProject: ->
+ return @rs.projects.get(@scope.projectId).then (project) =>
+ @scope.project = project
+ return project
+
+ loadNotifyPolicies: ->
+ return @rs.notifyPolicies.list().then (notifyPolicies) =>
+ @scope.notifyPolicies = notifyPolicies
+ return notifyPolicies
+
+ loadInitialData: ->
+ promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
+ @scope.projectId = data.project
+ return data
+
+ return promise.then(=> @.loadProject())
+ .then(=> @.loadNotifyPolicies())
+
+
+module.controller("UserNotificationsController", UserNotificationsController)
+
+#############################################################################
+## User Notifications Directive
+#############################################################################
+
+UserNotificationsDirective = () ->
+ link = ($scope, $el, $attrs) ->
+
+ $scope.$on "$destroy", ->
+ $el.off()
+
+ return {link:link}
+
+module.directive("tgUserNotifications", UserNotificationsDirective)
+
+#############################################################################
+## User Notifications List Directive
+#############################################################################
+
+UserNotificationsListDirective = ($repo, $confirm) ->
+ template = _.template("""
+ <% _.each(notifyPolicies, function (notifyPolicy, index) { %>
+