User profile integration

stable
Jesús Espino 2014-07-30 13:50:09 +02:00
parent 0c38ffc915
commit 8f5eead730
29 changed files with 764 additions and 118 deletions

View File

@ -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"})

View File

@ -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])

View File

@ -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"

View File

@ -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)

View File

@ -113,6 +113,9 @@ class Model
isModified: ->
return this._isModified
isAttributeModified: (attribute) ->
return @._modifiedAttrs[attribute]?
markSaved: () ->
@._isModified = false
@._attrs = @.getAttrs()

View File

@ -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>

View File

@ -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",

View File

@ -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", {})

View File

@ -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])

View File

@ -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])

View File

@ -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)

View File

@ -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])

View File

@ -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])

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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

View File

@ -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);

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;