diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3edcdd55..4246e874 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
- Moved from iconfont to SVG sprite icon system and redesign.
- Redesign 'Admin > Project > Modules' panel.
- Add badge to project owners
+- Limit of user per project.
### Misc
- Lots of small and not so small bugfixes.
diff --git a/app/coffee/modules/admin/memberships.coffee b/app/coffee/modules/admin/memberships.coffee
index bd3dc25e..9dbae4fe 100644
--- a/app/coffee/modules/admin/memberships.coffee
+++ b/app/coffee/modules/admin/memberships.coffee
@@ -47,11 +47,13 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
"$tgNavUrls",
"$tgAnalytics",
"tgAppMetaService",
- "$translate"
+ "$translate",
+ "tgCurrentUserService",
+ "$tgAuth"
]
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @analytics,
- @appMetaService, @translate) ->
+ @appMetaService, @translate, @currentUserService, @tgAuth) ->
bindMethods(@)
@scope.project = {}
@@ -63,6 +65,7 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
title = @translate.instant("ADMIN.MEMBERSHIPS.PAGE_TITLE", {projectName: @scope.project.name})
description = @scope.project.description
@appMetaService.setAll(title, description)
+ @._checkUsersLimit()
promise.then null, @.onInitialDataError.bind(@)
@@ -93,8 +96,11 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
loadInitialData: ->
promise = @.loadProject()
- promise.then =>
- @.loadMembers()
+
+ @q.all([
+ @.loadMembers(),
+ @tgAuth.refresh()
+ ])
return promise
@@ -106,6 +112,22 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai
addNewMembers: ->
@rootscope.$broadcast("membersform:new")
+ _checkUsersLimit: ->
+ @scope.canAddUsers = true
+ userData = @currentUserService.getUser().toJS()
+
+ if @currentUserService.canAddMoreMembersInPrivateProjects(@scope.projectId).valid == false
+ @.maxMembers = userData.max_members_private_projects
+ @scope.canAddUsers = false
+ else if @currentUserService.canAddMoreMembersInPublicProjects(@scope.projectId).valid == false
+ @.maxMembers = userData.max_members_private_projects
+ @scope.canAddUsers = false
+
+ limitUsersWarning: ->
+ title = @translate.instant("ADMIN.MEMBERSHIPS.LIMIT_USERS_WARNING")
+ message = @translate.instant("ADMIN.MEMBERSHIPS.LIMIT_USERS_WARNING_MESSAGE", {members: @.maxMembers})
+ icon = "/" + window._version + "/svg/icons/team-question.svg"
+ @confirm.success(title, message,icon)
module.controller("MembershipsController", MembershipsController)
diff --git a/app/coffee/modules/common/confirm.coffee b/app/coffee/modules/common/confirm.coffee
index c7c93e20..e9e6184e 100644
--- a/app/coffee/modules/common/confirm.coffee
+++ b/app/coffee/modules/common/confirm.coffee
@@ -161,11 +161,16 @@ class ConfirmService extends taiga.Service
return defered.promise
- success: (title, message) ->
+ success: (title, message, icon) ->
defered = @q.defer()
el = angular.element(".lightbox-generic-success")
+ el.find("img").remove()
+ detailImage = $('
').addClass('lb-icon').attr('src', icon)
+ if detailImage
+ el.find('section').prepend(detailImage)
+
# Render content
el.find(".title").html(title) if title
el.find(".message").html(message) if message
diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json
index 9b644e2d..c53fc45b 100644
--- a/app/locales/taiga/locale-en.json
+++ b/app/locales/taiga/locale-en.json
@@ -408,7 +408,9 @@
"TITLE": "Manage members",
"PAGE_TITLE": "Memberships - {{projectName}}",
"ADD_BUTTON": "+ New member",
- "ADD_BUTTON_TITLE": "Add new member"
+ "ADD_BUTTON_TITLE": "Add new member",
+ "LIMIT_USERS_WARNING": "Why can't I add more members",
+ "LIMIT_USERS_WARNING_MESSAGE": "Currently you can only have {{members}} members per project. If you want to add more members get in touch with the administrators"
},
"PROJECT_EXPORT": {
"TITLE": "Export",
diff --git a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade
index 50bc0a4d..47a2a7c0 100644
--- a/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade
+++ b/app/modules/navigation-bar/dropdown-project-list/dropdown-project-list.jade
@@ -4,8 +4,8 @@ a(href="", title="Projects", tg-nav="projects")
div.navbar-dropdown.dropdown-project-list
ul
- li(tg-repeat="project in vm.projects track by project.get('id')")
- a(href="#", tg-nav="project:project=project.get('slug')") {{::project.get("name")}}
+ li(tg-repeat="project in vm.projects track by project.get('id')")
+ a(href="#", tg-nav="project:project=project.get('slug')") {{::project.get("name")}}
a.see-more-projects-btn.button-gray(
href="#",
diff --git a/app/modules/services/current-user.service.coffee b/app/modules/services/current-user.service.coffee
index a14ffb8d..cfc5ad4b 100644
--- a/app/modules/services/current-user.service.coffee
+++ b/app/modules/services/current-user.service.coffee
@@ -118,6 +118,25 @@ class CurrentUserService
return @.projects
+ canAddMoreMembersInPrivateProjects: (projectId) ->
+ project = @.projects.get('all').find (project) -> project.get('id') == projectId
+ user = @.getUser()
+
+ if user.get('max_members_private_projects') != null && project.get('members').size >= user.get('max_members_private_projects')
+ return {valid: false, reason: 'max_members_private_projects', type: 'private_project'}
+
+ return {valid: true}
+
+ canAddMoreMembersInPublicProjects: (projectId) ->
+ project = @.projects.get('all').find (project) -> project.get('id') == projectId
+ user = @.getUser()
+
+ if user.get('max_members_public_projects') != null && project.get('members').size >= user.get('max_members_public_projects')
+ return {valid: false, reason: 'max_members_public_projects', type: 'public_project'}
+
+ return {valid: true}
+
+
canBePrivateProject: (projectId) ->
project = @.projects.get('all').find (project) -> project.get('id') == projectId
diff --git a/app/modules/services/current-user.service.spec.coffee b/app/modules/services/current-user.service.spec.coffee
index 32b51b0b..9747924b 100644
--- a/app/modules/services/current-user.service.spec.coffee
+++ b/app/modules/services/current-user.service.spec.coffee
@@ -204,6 +204,112 @@ describe "tgCurrentUserService", ->
done()
+ it "the user can't add more members in private projects", () ->
+ user = Immutable.fromJS({
+ id: 1,
+ name: "fake1",
+ max_members_private_projects: 2
+ })
+
+ projects = Immutable.fromJS({
+ all: [
+ {id: 1, name: "fake1"},
+ {id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: true},
+ {id: 3, name: "fake3"},
+ {id: 4, name: "fake4"}
+ ]
+ })
+
+ currentUserService._user = user
+ currentUserService._projects = projects
+
+ result = currentUserService.canAddMoreMembersInPrivateProjects(2)
+
+ expect(result).to.be.eql({
+ valid: false,
+ reason: 'max_members_private_projects',
+ type: 'private_project'
+ })
+
+ it "the user can add more members in private projects", () ->
+ user = Immutable.fromJS({
+ id: 1,
+ name: "fake1",
+ max_members_private_projects: 7
+ })
+
+ currentUserService._user = user
+
+ projects = Immutable.fromJS({
+ all: [
+ {id: 1, name: "fake1"},
+ {id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: true},
+ {id: 3, name: "fake3"},
+ {id: 4, name: "fake4"}
+ ]
+ })
+
+ currentUserService._projects = projects
+
+ result = currentUserService.canAddMoreMembersInPrivateProjects(2)
+
+ expect(result).to.be.eql({
+ valid: true
+ })
+
+ it "the user can't add more members in public projects", () ->
+ user = Immutable.fromJS({
+ id: 1,
+ name: "fake1",
+ max_members_public_projects: 2
+ })
+
+ projects = Immutable.fromJS({
+ all: [
+ {id: 1, name: "fake1"},
+ {id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: false},
+ {id: 3, name: "fake3"},
+ {id: 4, name: "fake4"}
+ ]
+ })
+
+ currentUserService._user = user
+ currentUserService._projects = projects
+
+ result = currentUserService.canAddMoreMembersInPublicProjects(2)
+
+ expect(result).to.be.eql({
+ valid: false,
+ reason: 'max_members_public_projects',
+ type: 'public_project'
+ })
+
+ it "the user can add more members in public projects", () ->
+ user = Immutable.fromJS({
+ id: 1,
+ name: "fake1",
+ max_members_public_projects: 7
+ })
+
+ projects = Immutable.fromJS({
+ all: [
+ {id: 1, name: "fake1"},
+ {id: 2, name: "fake2", members: [1, 2, 3, 4, 5], is_private: false},
+ {id: 3, name: "fake3"},
+ {id: 4, name: "fake4"}
+ ]
+ })
+
+ currentUserService._user = user
+ currentUserService._projects = projects
+
+ result = currentUserService.canAddMoreMembersInPublicProjects(2)
+
+ expect(result).to.be.eql({
+ valid: true
+ })
+
+
it "the user can't create private projects if they reach the maximum number of private projects", () ->
user = Immutable.fromJS({
id: 1,
@@ -214,7 +320,7 @@ describe "tgCurrentUserService", ->
currentUserService._user = user
- result = currentUserService.canCreatePrivateProjects(0)
+ result = currentUserService.canCreatePrivateProjects()
expect(result).to.be.eql({
valid: false,
diff --git a/app/partials/admin/admin-memberships.jade b/app/partials/admin/admin-memberships.jade
index 1944fb21..b97c5451 100644
--- a/app/partials/admin/admin-memberships.jade
+++ b/app/partials/admin/admin-memberships.jade
@@ -13,9 +13,19 @@ div.wrapper.memberships(ng-controller="MembershipsController as ctrl",
include ../includes/components/mainTitle
.action-buttons
- a.button-green(href="", title="{{ ADMIN.MEMBERSHIPS.ADD_BUTTON_TITLE | translate }}",
- ng-click="ctrl.addNewMembers()")
- span.text(translate="ADMIN.MEMBERSHIPS.ADD_BUTTON")
+ a.limit-users-warning(
+ ng-if="!canAddUsers"
+ translate="ADMIN.MEMBERSHIPS.LIMIT_USERS_WARNING"
+ translate-values="{members: ctrl.maxMembers}"
+ href=""
+ ng-click="ctrl.limitUsersWarning()"
+ )
+ button.button-green(
+ title="{{ ADMIN.MEMBERSHIPS.ADD_BUTTON_TITLE | translate }}",
+ ng-click="ctrl.addNewMembers()"
+ translate="ADMIN.MEMBERSHIPS.ADD_BUTTON"
+ ng-disabled="!canAddUsers"
+ )
include ../includes/modules/admin/admin-membership-table
diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss
index 77c4b389..1be18ede 100755
--- a/app/styles/components/buttons.scss
+++ b/app/styles/components/buttons.scss
@@ -44,7 +44,6 @@
}
}
}
-
.trans-button {
@extend %medium;
@extend %title;
@@ -61,12 +60,9 @@
color: $blackish;
}
}
-
-
.submit-button {
width: 100%;
}
-
.button-green,
a.button-green {
@extend %button;
@@ -77,7 +73,6 @@ a.button-green {
color: $white;
}
}
-
.button-gray,
a.button-gray {
@extend %button;
@@ -88,7 +83,6 @@ a.button-gray {
color: $white;
}
}
-
.button-blackish {
@extend %button;
background: $blackish;
@@ -98,7 +92,6 @@ a.button-gray {
color: $white;
}
}
-
.button-red {
@extend %button;
background: $red-light;
@@ -110,7 +103,6 @@ a.button-gray {
color: $white;
}
}
-
.button-block {
background: $white;
color: $red;
@@ -119,7 +111,6 @@ a.button-gray {
color: $white;
}
}
-
.button-bulk {
@extend %button;
background: $primary;
@@ -134,7 +125,6 @@ a.button-gray {
background: $primary-light;
}
}
-
.button-auth {
@extend %button;
background: $grayer;
diff --git a/app/styles/dependencies/helpers.scss b/app/styles/dependencies/helpers.scss
index e71a081c..d6d4d8aa 100644
--- a/app/styles/dependencies/helpers.scss
+++ b/app/styles/dependencies/helpers.scss
@@ -50,6 +50,11 @@
opacity: 0;
transition: opacity .3s ease;
}
+ .lb-icon {
+ @include svg-size(6rem);
+ display: block;
+ margin: 1rem auto;
+ }
.title {
text-align: center;
}
diff --git a/app/styles/layout/admin-memberships.scss b/app/styles/layout/admin-memberships.scss
index 7d7ad3c4..6a8d22fd 100644
--- a/app/styles/layout/admin-memberships.scss
+++ b/app/styles/layout/admin-memberships.scss
@@ -5,6 +5,11 @@
float: right;
}
}
+ .limit-users-warning {
+ @extend %small;
+ color: $primary;
+ margin-right: 1rem;
+ }
.check {
input { // IE needs smaller size
height: 40px;
diff --git a/app/styles/modules/common/lightbox.scss b/app/styles/modules/common/lightbox.scss
index 2cc9ed85..1a9b3379 100644
--- a/app/styles/modules/common/lightbox.scss
+++ b/app/styles/modules/common/lightbox.scss
@@ -1,8 +1,8 @@
.lightbox {
@extend %lightbox;
h2 {
- @extend %larger;
- @extend %text;
+ @extend %xlarge;
+ @extend %light;
}
}
diff --git a/app/svg/icons/team-question.svg b/app/svg/icons/team-question.svg
new file mode 100644
index 00000000..e8678f64
--- /dev/null
+++ b/app/svg/icons/team-question.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg
index a0283ab7..68d91a7e 100644
--- a/app/svg/sprite.svg
+++ b/app/svg/sprite.svg
@@ -2,32 +2,38 @@
iocaine
-
lock
-
promote
-
trash
-
unlock
-
client-requirement
-
@@ -36,7 +42,8 @@
team-requirement
-
@@ -49,7 +56,8 @@
bulk
-
@@ -58,12 +66,14 @@
dashboard
-
discover
-
@@ -72,12 +82,14 @@
exclamation
-
flag
-
@@ -94,7 +106,8 @@
issues
-
@@ -103,32 +116,38 @@
like-empty
-
like
-
question
-
search
-
settings
-
team
-
@@ -145,22 +164,26 @@
unwatch
-
user
-
wiki
-
archive
-
@@ -181,7 +204,8 @@
attachment
-
@@ -198,7 +222,8 @@
bug
-
@@ -211,12 +236,14 @@
clipboard
-
document
-
@@ -233,7 +260,8 @@
error
-
@@ -246,27 +274,32 @@
github
-
maximize
-
minimize
-
move
-
reload
-
@@ -283,12 +316,14 @@
writer
-
watch
-
@@ -297,37 +332,44 @@
task
-
api
-
customize
-
feel-love
-
highly-designed
-
integration
-
lend-hand
-
@@ -336,17 +378,20 @@
smiley
-
social-management
-
speak-up
-
@@ -359,11 +404,19 @@
Looking for people
-
+
Owner Badge
-
+
+
+
+ team-question
+