User profile integration
parent
0c38ffc915
commit
8f5eead730
|
@ -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"})
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -113,6 +113,9 @@ class Model
|
|||
isModified: ->
|
||||
return this._isModified
|
||||
|
||||
isAttributeModified: (attribute) ->
|
||||
return @._modifiedAttrs[attribute]?
|
||||
|
||||
markSaved: () ->
|
||||
@._isModified = false
|
||||
@._attrs = @.getAttrs()
|
||||
|
|
|
@ -155,12 +155,13 @@ ProjectMenuDirective = ($log, $compile, $auth, $rootscope, $tgAuth, $location) -
|
|||
<div class="user">
|
||||
<div class="user-settings">
|
||||
<ul class="popover">
|
||||
<li><a href="" title="Account settings", tg-nav="user-settings-user-profile:project=project.slug">Account settings</a></li>
|
||||
<li><a href="" title="Change profile photo", tg-nav="user-settings-user-avatar:project=project.slug">Change profile photo</a></li>
|
||||
<li><a href="" title="User Profile", tg-nav="user-settings-user-profile:project=project.slug">User Profile</a></li>
|
||||
<li><a href="" title="Change Password", tg-nav="user-settings-user-change-password:project=project.slug">Change Password</a></li>
|
||||
<li><a href="" title="Notifications", tg-nav="user-settings-mail-notifications:project=project.slug">Notifications</a></li>
|
||||
<li><a href="" title="Logout" class="logout">Logout</a></li>
|
||||
</ul>
|
||||
<a href="" title="User preferences" class="avatar" id="nav-user-settings">
|
||||
<img src="<%- user.photo %>" alt="<%- user.full_name_display %>" />
|
||||
<img src="{{ user.photo }}" alt="{{ user.full_name_display }}" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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", {})
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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])
|
|
@ -0,0 +1,55 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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])
|
|
@ -0,0 +1,93 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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)
|
|
@ -0,0 +1,66 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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])
|
|
@ -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("<strong>Check your inbox!</strong><br />
|
||||
We have sent a mail to your account<br />
|
||||
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])
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
###
|
||||
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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) { %>
|
||||
<div class="policy-table-row" data-index="<%- index %>">
|
||||
<div class="policy-table-project"><span><%- notifyPolicy.project_name %></span></div>
|
||||
<div class="policy-table-all">
|
||||
<fieldset>
|
||||
<input type="radio"
|
||||
name="policy-<%- notifyPolicy.id %>" id="policy-all-<%- notifyPolicy.id %>"
|
||||
value="2" <% if (notifyPolicy.notify_level == 2) { %>checked="checked"<% } %>/>
|
||||
<label for="policy-all-<%- notifyPolicy.id %>">All</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="policy-table-involved">
|
||||
<fieldset>
|
||||
<input type="radio"
|
||||
name="policy-<%- notifyPolicy.id %>" id="policy-involved-<%- notifyPolicy.id %>"
|
||||
value="1" <% if (notifyPolicy.notify_level == 1) { %>checked="checked"<% } %> />
|
||||
<label for="policy-involved-<%- notifyPolicy.id %>">Involved</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="policy-table-none">
|
||||
<fieldset>
|
||||
<input type="radio"
|
||||
name="policy-<%- notifyPolicy.id %>" id="policy-none-<%- notifyPolicy.id %>"
|
||||
value="3" <% if (notifyPolicy.notify_level == 3) { %>checked="checked"<% } %> />
|
||||
<label for="policy-none-<%- notifyPolicy.id %>">None</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
""")
|
||||
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])
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
div.change-email-form-container(tg-change-email)
|
||||
p.change-password-text
|
||||
strong Change your email <br />
|
||||
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")
|
|
@ -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
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue