From 8f5eead7308f445658dc7c74d3cc6f258b4388dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 30 Jul 2014 13:50:09 +0200 Subject: [PATCH] User profile integration --- app/coffee/app.coffee | 6 + app/coffee/modules/auth.coffee | 54 ++++++- app/coffee/modules/base.coffee | 2 + app/coffee/modules/base/confirm.coffee | 4 +- app/coffee/modules/base/model.coffee | 3 + app/coffee/modules/nav.coffee | 7 +- app/coffee/modules/resources.coffee | 4 + .../modules/resources/memberships.coffee | 5 + .../modules/resources/notify-policies.coffee | 40 +++++ .../modules/resources/user-settings.coffee | 55 +++++++ .../user-settings/change-password.coffee | 93 +++++++++++ .../modules/user-settings/lightboxes.coffee | 66 ++++++++ app/coffee/modules/user-settings/main.coffee | 77 +++++++++ .../user-settings/notifications.coffee | 153 ++++++++++++++++++ app/partials/change-email.jade | 14 ++ app/partials/mail-notifications.jade | 5 +- app/partials/user-avatar.jade | 20 --- app/partials/user-change-password.jade | 32 ++++ app/partials/user-profile.jade | 47 +++--- .../views/modules/change-email-form.jade | 12 ++ .../views/modules/lightbox-use-gravatar.jade | 11 ++ .../views/modules/user-settings-menu.jade | 6 +- .../mail-notifications-table.jade | 31 +--- app/styles/main.scss | 4 +- app/styles/modules/common/lightbox.scss | 27 ++++ .../mail-notifications-table.scss | 22 +-- .../modules/user-settings/user-avatar.scss | 22 --- .../user-settings/user-change-password.scss | 20 +++ .../modules/user-settings/user-profile.scss | 40 ++++- 29 files changed, 764 insertions(+), 118 deletions(-) create mode 100644 app/coffee/modules/resources/notify-policies.coffee create mode 100644 app/coffee/modules/resources/user-settings.coffee create mode 100644 app/coffee/modules/user-settings/change-password.coffee create mode 100644 app/coffee/modules/user-settings/lightboxes.coffee create mode 100644 app/coffee/modules/user-settings/notifications.coffee create mode 100644 app/partials/change-email.jade delete mode 100644 app/partials/user-avatar.jade create mode 100644 app/partials/user-change-password.jade create mode 100644 app/partials/views/modules/change-email-form.jade create mode 100644 app/partials/views/modules/lightbox-use-gravatar.jade delete mode 100644 app/styles/modules/user-settings/user-avatar.scss create mode 100644 app/styles/modules/user-settings/user-change-password.scss diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 3d9c82df..ae81e538 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -103,12 +103,18 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide) -> $routeProvider.when("/project/:pslug/user-settings/user-profile", {templateUrl: "/partials/user-profile.html"}) + $routeProvider.when("/project/:pslug/user-settings/user-change-password", + {templateUrl: "/partials/user-change-password.html"}) + $routeProvider.when("/project/:pslug/user-settings/user-avatar", {templateUrl: "/partials/user-avatar.html"}) $routeProvider.when("/project/:pslug/user-settings/mail-notifications", {templateUrl: "/partials/mail-notifications.html"}) + $routeProvider.when("/change-email/:email_token", + {templateUrl: "/partials/change-email.html"}) + # Auth $routeProvider.when("/login", {templateUrl: "/partials/login.html"}) $routeProvider.when("/register", {templateUrl: "/partials/register.html"}) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index 96bbb9c0..4d89b2af 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -54,9 +54,11 @@ class AuthService extends taiga.Service @rootscope.auth = user @rootscope.$broadcast("i18n:change", user.default_language) @storage.set("userInfo", user.getAttrs()) + @rootscope.user = user clear: -> @rootscope.auth = null + @rootscope.user = null @storage.remove("userInfo") setToken: (token) -> @@ -93,6 +95,7 @@ class AuthService extends taiga.Service logout: -> @.removeToken() + @.clear() register: (data, type, existing) -> url = @urls.resolve("auth-register") @@ -121,21 +124,19 @@ class AuthService extends taiga.Service forgotPassword: (data) -> url = @urls.resolve("users-password-recovery") - data = _.clone(data, false) - @.removeToken() - return @http.post(url, data) - changePasswordFromRecovery: (data) -> url = @urls.resolve("users-change-password-from-recovery") - data = _.clone(data, false) - @.removeToken() + return @http.post(url, data) + changeEmail: (data) -> + url = @urls.resolve("users-change-email") + data = _.clone(data, false) return @http.post(url, data) @@ -393,3 +394,44 @@ InvitationDirective = ($auth, $confirm, $location, $params) -> module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$location", "$routeParams", InvitationDirective]) + +################### +## Change Email +################### + +ChangeEmailDirective = ($repo, $model, $auth, $confirm, $location, $params) -> + link = ($scope, $el, $attrs) -> + $scope.data = {} + $scope.data.email_token = $params.email_token + form = $el.find("form").checksley() + + onSuccessSubmit = (response) -> + $repo.queryOne("users", $auth.getUser().id).then (data) => + $auth.setUser(data) + $location.path("/") # TODO: Use the future 'urls' service + $confirm.success("Our Oompa Loompas updated your email") #TODO: i18n + + onErrorSubmit = (response) -> + console.log "ASDASDASDASD" + $confirm.notify("error", "One of our Oompa Loompas says + '#{response.data._error_message}'.") #TODO: i18n + + submit = -> + if not form.validate() + return + + promise = $auth.changeEmail($scope.data) + promise.then(onSuccessSubmit, onErrorSubmit) + + $el.on "submit", (event) -> + event.preventDefault() + submit() + + $el.on "click", "a.button-change-email", (event) -> + event.preventDefault() + submit() + + return {link:link} + +module.directive("tgChangeEmail", ["$tgRepo", "$tgModel", "$tgAuth", "$tgConfirm", "$location", "$routeParams", + ChangeEmailDirective]) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 55033cf4..d51ebf40 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -47,6 +47,7 @@ urls = { "login": "/login" "forgot-password": "/forgot-password" "change-password": "/change-password/:token" + "change-email": "/change-email/:token" "register": "/register" "invitation": "/invitation/:token" "create-project": "/create-project" @@ -91,6 +92,7 @@ urls = { # User settings "user-settings-user-profile": "/project/:project/user-settings/user-profile" + "user-settings-user-change-password": "/project/:project/user-settings/user-change-password" "user-settings-user-avatar": "/project/:project/user-settings/user-avatar" "user-settings-mail-notifications": "/project/:project/user-settings/mail-notifications" diff --git a/app/coffee/modules/base/confirm.coffee b/app/coffee/modules/base/confirm.coffee index e0ccdb7c..c26f3356 100644 --- a/app/coffee/modules/base/confirm.coffee +++ b/app/coffee/modules/base/confirm.coffee @@ -48,8 +48,8 @@ class ConfirmService extends taiga.Service @.el.off(".confirm-dialog") delete @.el - ask: (title, subtitle) -> - @.el = angular.element(".lightbox_confirm-delete") + ask: (title, subtitle, lightboxSelector=".lightbox_confirm-delete") -> + @.el = angular.element(lightboxSelector) # Render content @.el.find("h2.title").html(title) diff --git a/app/coffee/modules/base/model.coffee b/app/coffee/modules/base/model.coffee index d3e90d95..20bd5022 100644 --- a/app/coffee/modules/base/model.coffee +++ b/app/coffee/modules/base/model.coffee @@ -113,6 +113,9 @@ class Model isModified: -> return this._isModified + isAttributeModified: (attribute) -> + return @._modifiedAttrs[attribute]? + markSaved: () -> @._isModified = false @._attrs = @.getAttrs() diff --git a/app/coffee/modules/nav.coffee b/app/coffee/modules/nav.coffee index d8961738..7e64bb38 100644 --- a/app/coffee/modules/nav.coffee +++ b/app/coffee/modules/nav.coffee @@ -155,12 +155,13 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location) -
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) { %> +
+
<%- notifyPolicy.project_name %>
+
+
+ checked="checked"<% } %>/> + +
+
+
+
+ checked="checked"<% } %> /> + +
+
+
+
+ checked="checked"<% } %> /> + +
+
+
+ <% }) %> + """) + link = ($scope, $el, $attrs) -> + + render = -> + $el.off() + $el.html(template({notifyPolicies: $scope.notifyPolicies})) + $el.on "change", "input[type=radio]", (event) -> + target = angular.element(event.currentTarget) + policyIndex = target.parents(".policy-table-row").data('index') + policy = $scope.notifyPolicies[policyIndex] + + prev_level = policy.notify_level + policy.notify_level = parseInt(target.val(), 10) + + $repo.save(policy).then -> + $repo.save(policy).then null, -> + $confirm.notify("error") + target.parents(".policy-table-row").find("input[value=#{prev_level}]").prop("checked", true) + + $scope.$on "$destroy", -> + $el.off() + + + bindOnce($scope, $attrs.ngModel, render) + + return {link:link} + +module.directive("tgUserNotificationsList", ["$tgRepo", "$tgConfirm", UserNotificationsListDirective]) diff --git a/app/partials/change-email.jade b/app/partials/change-email.jade new file mode 100644 index 00000000..e5e199c4 --- /dev/null +++ b/app/partials/change-email.jade @@ -0,0 +1,14 @@ +extends dummy-layout + +block head + title Taiga Project management web application with scrum in mind! + +block content + div.wrapper + div.login-main + div.login-container + h1.logo + img(src="/images/logo.png", alt="TAIGA") + p.tagline Project management web application with scrum in mind! + + include views/modules/change-email-form diff --git a/app/partials/mail-notifications.jade b/app/partials/mail-notifications.jade index a527c5bf..1d51008a 100644 --- a/app/partials/mail-notifications.jade +++ b/app/partials/mail-notifications.jade @@ -4,14 +4,15 @@ block head title Taiga Project management web application with scrum in mind! block content - div.wrapper(tg-user-profile, ng-controller="UserSettingsController as ctrl", + div.wrapper(tg-user-notifications, ng-controller="UserNotificationsController as ctrl", ng-init="section='mail-notifications'") sidebar.menu-secondary.sidebar(tg-user-settings-navigation="mail-notifications") include views/modules/user-settings-menu section.main.admin-roles header - include views/components/mainTitle + h1 + span.green(tg-bo-html="sectionName") p.total Notifications By Mail diff --git a/app/partials/user-avatar.jade b/app/partials/user-avatar.jade deleted file mode 100644 index d16ae3d2..00000000 --- a/app/partials/user-avatar.jade +++ /dev/null @@ -1,20 +0,0 @@ -extends dummy-layout - -block head - title Taiga Project management web application with scrum in mind! - -block content - div.wrapper(tg-user-avatar, ng-controller="UserSettingsController as ctrl", - ng-init="section='user-settings'") - sidebar.menu-secondary.sidebar(tg-user-settings-navigation="avatar") - include views/modules/user-settings-menu - - section.main.user-avatar - header - include views/components/mainTitle - - form - fieldset - img.avatar(tg-bo-src="user.photo" alt="avatar") - a.button.button-green Cambiar - input(type="file", id="user-avatar", ng-value="user.photo", class="hidden") diff --git a/app/partials/user-change-password.jade b/app/partials/user-change-password.jade new file mode 100644 index 00000000..c6ebd787 --- /dev/null +++ b/app/partials/user-change-password.jade @@ -0,0 +1,32 @@ +extends dummy-layout + +block head + title Taiga Project management web application with scrum in mind! + +block content + div.wrapper(tg-user-change-password, ng-controller="UserChangePasswordController as ctrl", + ng-init="section='user-settings'") + sidebar.menu-secondary.sidebar(tg-user-settings-navigation="change-password") + include views/modules/user-settings-menu + + section.main.user-change-password + header + h1 + span.green(tg-bo-html="sectionName") + + form + fieldset + label(for="current-password") Current Password + input(type="password", placeholder="Password", id="current-password", ng-model="currentPassword") + fieldset + label(for="new-password") New Password + input(type="password", placeholder="New Password", id="new-password", ng-model="newPassword1") + fieldset + label(for="retype-password") Retype Password + input(type="password", placeholder="Retype Password", id="retype-password", ng-model="newPassword2") + fieldset + input(type="submit", class="hidden") + a.button.button-green(href="", ng-click="ctrl.save()") Save + + div.lightbox.lightbox-delete-account.hidden + include views/modules/lightbox-delete-account diff --git a/app/partials/user-profile.jade b/app/partials/user-profile.jade index 4ab47b54..ed691953 100644 --- a/app/partials/user-profile.jade +++ b/app/partials/user-profile.jade @@ -11,25 +11,36 @@ block content section.main.user-profile header - include views/components/mainTitle + h1 + span.green(tg-bo-html="sectionName") form - fieldset - label(for="user-email") Email - input(type="text", placeholder="Email", id="user-email", ng-value="user.email") - fieldset - label(for="current-password") Current Password - input(type="password", placeholder="Password", id="current-password") - fieldset - label(for="new-password") New Password - input(type="password", placeholder="New Password", id="new-password") - fieldset - label(for="retype-password") Retype Password - input(type="password", placeholder="Retype Password", id="retype-password") - fieldset - input(type="submit", class="hidden") - a.button.button-green(href="") Save - a.delete-account(href="", title="Delete Taiga account") Delete Taiga account + fieldset.avatar(tg-user-avatar) + img.avatar(ng-src="{{user.big_photo}}" alt="avatar") + input(type="file", id="avatar-field", tg-avatar-model="avatarAttachment" class="hidden") + p The image will be cropped to 80x80 size. + a.button.button-green.change Cambiar + a.use-gravatar Use gravatar image - div.lightbox.lightbox-delete-account.hidden + fieldset + label(for="email") Email + input(type="text", placeholder="email", id="email", ng-model="user.email", data-type="email") + + fieldset + label(for="name") Name + input(type="text", placeholder="Name", id="name", ng-model="user.full_name") + + fieldset + label(for="bio") Bio + textarea(placeholder="Bio", id="bio", ng-model="user.bio") + + fieldset.submit + input(type="submit", class="hidden") + a.button.button-green.save-profile(href="") Save + a.delete-account(href="", title="Delete Taiga account", ng-click="ctrl.openDeleteLightbox()") Delete Taiga account + + div.lightbox.lightbox-delete-account.hidden(tg-lb-delete-user) include views/modules/lightbox-delete-account + + div.lightbox.lightbox_confirm-use-gravatar.hidden + include views/modules/lightbox-use-gravatar diff --git a/app/partials/views/modules/change-email-form.jade b/app/partials/views/modules/change-email-form.jade new file mode 100644 index 00000000..ccfbd79a --- /dev/null +++ b/app/partials/views/modules/change-email-form.jade @@ -0,0 +1,12 @@ +div.change-email-form-container(tg-change-email) + p.change-password-text + strong Change your email
+ span One click more and your email will be updated! + + form(ng-submit="ctrl.submit()") + fieldset + input(type="hidden", name="email_token", ng-model="data.email_token", data-required="true", + placeholder="change email token") + + a.button.button-change-email.button-gray(href="", title="Change email") Change email + input(type="submit", style="display:none") diff --git a/app/partials/views/modules/lightbox-use-gravatar.jade b/app/partials/views/modules/lightbox-use-gravatar.jade new file mode 100644 index 00000000..464d9e9e --- /dev/null +++ b/app/partials/views/modules/lightbox-use-gravatar.jade @@ -0,0 +1,11 @@ +a.close(href="", title="close") + span.icon.icon-delete +form + h2.title Use gravatar as image + p + span.subtitle You really want to delete your current photo and use the gravatar image? + div.options + a.button.button-green(href="", title="Accept") + span Accept + a.button.button-red(href="", title="Delete") + span Cancel diff --git a/app/partials/views/modules/user-settings-menu.jade b/app/partials/views/modules/user-settings-menu.jade index 75a6b5fa..2c1e90ce 100644 --- a/app/partials/views/modules/user-settings-menu.jade +++ b/app/partials/views/modules/user-settings-menu.jade @@ -8,9 +8,9 @@ section.admin-menu a(href="", tg-nav="user-settings-user-profile:project=project.slug") span.title User profile span.icon.icon-arrow-right - li#usersettingsmenu-avatar - a(href="" tg-nav="user-settings-user-avatar:project=project.slug") - span.title User avatar + li#usersettingsmenu-change-password + a(href="" tg-nav="user-settings-user-change-password:project=project.slug") + span.title Change password span.icon.icon-arrow-right li#usersettingsmenu-mail-notifications a(href="", tg-nav="user-settings-mail-notifications:project=project.slug") diff --git a/app/partials/views/modules/user-settings/mail-notifications-table.jade b/app/partials/views/modules/user-settings/mail-notifications-table.jade index aa16a5e1..56c0af7d 100644 --- a/app/partials/views/modules/user-settings/mail-notifications-table.jade +++ b/app/partials/views/modules/user-settings/mail-notifications-table.jade @@ -1,27 +1,12 @@ -section.mail-notifications-table - div.mail-notifications-table-header - div.mail-notifications-table-row - div.mail-notifications-table-project +section.policy-table + div.policy-table-header + div.policy-table-row + div.policy-table-project span Project - div.mail-notifications-table-all + div.policy-table-all span Receive All - div.mail-notifications-table-involved + div.policy-table-involved span Only Involved - div.mail-notifications-table-none + div.policy-table-none span No notifications - div.mail-notifications-table-body - div.mail-notifications-table-row - div.mail-notifications-table-project - span Decathlon - div.mail-notifications-table-all - fieldset - input(type="radio", name="mail-notifications", id="notifications-all") - label(for="notifications-all") All - div.mail-notifications-table-involved - fieldset - input(type="radio", name="mail-notifications", id="notifications-involved") - label(for="notifications-involved") Involved - div.mail-notifications-table-none - fieldset - input(type="radio", name="mail-notifications", id="notifications-none") - label(for="notifications-none") None + div.policy-table-body(tg-user-notifications-list, ng-model="notifyPolicies") diff --git a/app/styles/main.scss b/app/styles/main.scss index 546e319f..d99dfca0 100755 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -105,9 +105,9 @@ $prefix-for-spec: true; @import 'modules/admin/project-values'; //Modules user Settings -@import 'modules/user-settings/mail-notifications-table'; @import 'modules/user-settings/user-profile'; -@import 'modules/user-settings/user-avatar'; +@import 'modules/user-settings/user-change-password'; +@import 'modules/user-settings/mail-notifications-table'; //################################################# // Layout diff --git a/app/styles/modules/common/lightbox.scss b/app/styles/modules/common/lightbox.scss index f17dde71..73280156 100644 --- a/app/styles/modules/common/lightbox.scss +++ b/app/styles/modules/common/lightbox.scss @@ -261,6 +261,33 @@ } } +.lightbox_confirm-use-gravatar { + form { + @include table-flex-child(0, 420px, 0, 420px); + } + .delete-question, + .subtitle { + display: block; + line-height: 2rem; + text-align: center; + } + .subtitle { + @extend %large; + @extend %title; + } + .options { + @include table-flex(); + a { + @include table-flex-child(1, 0, 0); + padding: 8px 0; + text-align: center; + &:first-child { + margin-right: .5rem; + } + } + } +} + .lightbox-delete-account { form { @include table-flex-child(0, 420px, 0, 420px); diff --git a/app/styles/modules/user-settings/mail-notifications-table.scss b/app/styles/modules/user-settings/mail-notifications-table.scss index aff935ad..f4bb4913 100644 --- a/app/styles/modules/user-settings/mail-notifications-table.scss +++ b/app/styles/modules/user-settings/mail-notifications-table.scss @@ -1,28 +1,28 @@ -.mail-notifications-table { - .mail-notifications-table-row { +.policy-table { + .policy-table-row { @include table-flex(stretch, center, flex, row, wrap, center); border-bottom: 1px solid $whitish; } - .mail-notifications-table-header { + .policy-table-header { @extend %bold; border-bottom: 2px solid $gray-light; } - .mail-notifications-table-project , - .mail-notifications-table-all, - .mail-notifications-table-involved, - .mail-notifications-table-none { + .policy-table-project , + .policy-table-all, + .policy-table-involved, + .policy-table-none { padding: 1rem; } - .mail-notifications-table-project { + .policy-table-project { @include table-flex-child(3, 0, 0); } - .mail-notifications-table-all, - .mail-notifications-table-involved, - .mail-notifications-table-none { + .policy-table-all, + .policy-table-involved, + .policy-table-none { @include table-flex-child(1, 0, 0); } input { diff --git a/app/styles/modules/user-settings/user-avatar.scss b/app/styles/modules/user-settings/user-avatar.scss deleted file mode 100644 index 73726aad..00000000 --- a/app/styles/modules/user-settings/user-avatar.scss +++ /dev/null @@ -1,22 +0,0 @@ -.user-avatar { - fieldset { - margin-bottom: 1rem; - width: 50%; - &:last-child { - margin-top: 2rem; - } - } - img { - border: 2px solid $white; - border-radius: 8%; - width: 300px; - } - .button { - bottom: 20px; - cursor: pointer; - height: 34px; - left: 85px; - position: absolute; - width: 130px; - } -} diff --git a/app/styles/modules/user-settings/user-change-password.scss b/app/styles/modules/user-settings/user-change-password.scss new file mode 100644 index 00000000..e31f3d19 --- /dev/null +++ b/app/styles/modules/user-settings/user-change-password.scss @@ -0,0 +1,20 @@ +.user-change-password { + fieldset { + margin-bottom: 1rem; + width: 50%; + &:last-child { + margin-top: 2rem; + } + } + label { + @extend %title; + display: block; + margin-bottom: .5rem; + } + .button-green { + color: $white; + display: block; + padding: 12px; + text-align: center; + } +} diff --git a/app/styles/modules/user-settings/user-profile.scss b/app/styles/modules/user-settings/user-profile.scss index 226aca76..379c8e9c 100644 --- a/app/styles/modules/user-settings/user-profile.scss +++ b/app/styles/modules/user-settings/user-profile.scss @@ -1,15 +1,43 @@ .user-profile { - input[type="text"], - input[type="email"] { - @extend %title; - background-color: $very-light-gray; - } fieldset { margin-bottom: 1rem; - width: 50%; + width: 37%; &:last-child { margin-top: 2rem; } + &.avatar { + float: left; + margin: 0 1% 0 0; + width: 12%; + img { + border: 2px solid $white; + border-radius: 8%; + width: 100%; + } + p { + @extend %xsmall; + margin-bottom: 0; + text-align: center; + } + .button { + bottom: 15px; + cursor: pointer; + height: 34px; + margin: 5px 10%; + padding: 7px; + width: 80%; + } + .use-gravatar { + @extend %small; + cursor: pointer; + display: inline-block; + text-align: center; + width: 100%; + } + } + &.submit { + width: 50%; + } } label { @extend %title;