diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe2d59b..5ba53a2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Redesign 'Admin > Project > Modules' panel. - Add badge to project owners - Limit of user per project. +- Redesign of the create project wizard ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/modules/base/navurls.coffee b/app/coffee/modules/base/navurls.coffee index 85fee430..28fa20de 100644 --- a/app/coffee/modules/base/navurls.coffee +++ b/app/coffee/modules/base/navurls.coffee @@ -59,7 +59,7 @@ module.service("$tgNavUrls", NavigationUrlsService) ## Navigation Urls Directive ############################################################################# -NavigationUrlsDirective = ($navurls, $auth, $q, $location) -> +NavigationUrlsDirective = ($navurls, $auth, $q, $location, lightboxService) -> # Example: # link(tg-nav="project-backlog:project='sss',") @@ -157,9 +157,11 @@ NavigationUrlsDirective = ($navurls, $auth, $q, $location) -> when 2 window.open fullUrl + lightboxService.closeAll() + $scope.$on "$destroy", -> $el.off() return {link: link} -module.directive("tgNav", ["$tgNavUrls", "$tgAuth", "$q", "$tgLocation", NavigationUrlsDirective]) +module.directive("tgNav", ["$tgNavUrls", "$tgAuth", "$q", "$tgLocation", "lightboxService", NavigationUrlsDirective]) diff --git a/app/coffee/modules/projects/create-project-restriction.directive.coffee b/app/coffee/modules/projects/create-project-restriction.directive.coffee new file mode 100644 index 00000000..66564563 --- /dev/null +++ b/app/coffee/modules/projects/create-project-restriction.directive.coffee @@ -0,0 +1,8 @@ +module = angular.module("taigaProject") + +createProjectRestrictionDirective = () -> + return { + templateUrl: "project/wizard-restrictions.html" + } + +module.directive('tgCreateProjectRestriction', [createProjectRestrictionDirective]) diff --git a/app/coffee/modules/projects/lightboxes.coffee b/app/coffee/modules/projects/lightboxes.coffee index c5561ae8..774bb866 100644 --- a/app/coffee/modules/projects/lightboxes.coffee +++ b/app/coffee/modules/projects/lightboxes.coffee @@ -29,12 +29,16 @@ debounce = @.taiga.debounce module = angular.module("taigaProject") -CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, currentUserService) -> +CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, currentUserService, $auth) -> link = ($scope, $el, attrs) -> $scope.data = {} $scope.templates = [] currentLoading = null + $auth.refresh() + $scope.canCreatePrivateProjects = currentUserService.canCreatePrivateProjects() + $scope.canCreatePublicProjects = currentUserService.canCreatePublicProjects() + form = $el.find("form").checksley({"onlyOneErrorElement": true}) onSuccessSubmit = (response) -> @@ -58,10 +62,6 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project selectors = [] for error_field in _.keys(response) selectors.push("[name=#{error_field}]") - $el.find(".active").removeClass("active") - error_step = $el.find(selectors.join(",")).first().parents(".wizard-step") - error_step.addClass("active") - $el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(error_step.data("step")) submit = (event) => event.preventDefault() @@ -77,7 +77,9 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project promise.then(onSuccessSubmit, onErrorSubmit) openLightbox = -> - $scope.data = {} + $scope.data = { + is_private: false + } if !$scope.templates.length $rs.projects.templates().then (result) => @@ -86,40 +88,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project else $scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id - $el.find(".active").removeClass("active") - $el.find(".create-step1").addClass("active") - lightboxService.open($el) - timeout 600, -> - $el.find(".progress-bar").addClass('step1') - - $el.on "click", ".button-next", (event) -> - event.preventDefault() - - current = $el.find(".active") - - valid = true - for field in form.fields - if current.find("[name=#{field.element.attr('name')}]").length - valid = field.validate() != false and valid - - if not valid - return - - next = current.next() - current.toggleClass('active') - next.toggleClass('active') - step = next.data('step') - $el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(step) - - $el.on "click", ".button-prev", (event) -> - event.preventDefault() - current = $el.find(".active") - prev = current.prev() - current.toggleClass('active') - prev.toggleClass('active') - step = prev.data('step') - $el.find('.progress-bar').removeClass().addClass('progress-bar').addClass(step) submitButton = $el.find(".submit-button") @@ -145,7 +114,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm", "$location", "$tgNavUrls", "$tgResources", "$projectUrl", "$tgLoading", - "lightboxService", "$cacheFactory", "$translate", "tgCurrentUserService", CreateProject]) + "lightboxService", "$cacheFactory", "$translate", "tgCurrentUserService", "$tgAuth", CreateProject]) ############################################################################# diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 368d9784..608e67fa 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -470,9 +470,9 @@ "CHANGE_LOGO": "Change logo", "ACTION_USE_DEFAULT_LOGO": "Use default image", "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The project exceeds the maximun members number in private projects", + "MAX_PRIVATE_PROJECTS_MEMBERS": "The project exceeds the maximum members number in private projects", "MAX_PUBLIC_PROJECTS": "You've reached the maximum number of public projects", - "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds the maximun members number in public projects" + "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds the maximum members number in public projects" }, "REPORTS": { "TITLE": "Reports", @@ -1324,12 +1324,18 @@ } }, "WIZARD": { - "SECTION_TITLE_CHOOSE_TEMPLATE": "Choose a template", - "CHOOSE_TEMPLATE_TEXT": "Which template would fit better in your project?", "SECTION_TITLE_CREATE_PROJECT": "Create Project", "CREATE_PROJECT_TEXT": "Fresh and clean. So exciting!", - "PROGRESS_TEMPLATE_SELECTION": "Template selection", - "PROGRESS_NAME_DESCRIPTION": "Name and description" + "CHOOSE_TEMPLATE": "Which template would fit better in your project?", + "CHOOSE_TEMPLATE_TITLE": "More info about project templates", + "CHOOSE_TEMPLATE_INFO": "More info", + "PROJECT_DETAILS": "Project Details", + "PUBLIC_PROJECT": "Public Project", + "PRIVATE_PROJECT": "Private Project", + "CREATE_PROJECT": "Create project", + "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects", + "MAX_PUBLIC_PROJECTS": "You've reached the maximum number of public projects", + "CHANGE_PLANS": "change plans" }, "WIKI": { "PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}", diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index a7c2ab65..5aca481e 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -57,7 +57,7 @@ class ProjectsService extends taiga.Service newProject: -> @lightboxFactory.create("tg-lb-create-project", { - "class": "wizard-create-project" + "class": "wizard-create-project lightbox" }) bulkUpdateProjectsOrder: (sortData) -> diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 046d1d23..6b49303b 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -79,7 +79,7 @@ describe "tgProjectsService", -> projectsService.newProject() expect(mocks.lightboxFactory.create).to.have.been.calledWith("tg-lb-create-project", { - "class": "wizard-create-project" + "class": "wizard-create-project lightbox" }) it "bulkUpdateProjectsOrder and then fetch projects again", () -> diff --git a/app/partials/project/wizard-create-project.jade b/app/partials/project/wizard-create-project.jade index f32cc8c0..ca9808be 100644 --- a/app/partials/project/wizard-create-project.jade +++ b/app/partials/project/wizard-create-project.jade @@ -1,77 +1,94 @@ +svg.close.icon.icon-close(title="{{'COMMON.CLOSE' | translate}}") + use(xlink:href="#icon-close") form - section.wizard-step.create-step1(data-step="step1") - div.title - h1(translate="WIZARD.SECTION_TITLE_CHOOSE_TEMPLATE") - p(translate="WIZARD.CHOOSE_TEMPLATE_TEXT") - div.template-wrapper - div.template-inner - fieldset(ng-repeat="template in templates") - input( - type="radio" - name="template" - id="template-{{ template.id }}" - ng-value='template.id' - ng-model="data.creation_template" - data-required="true" - ) - label.backlog(for="template-{{ template.id }}") - svg.icon(ng-class="'icon-'+template.slug") - use(xlink:href="{{'#icon-' + template.slug }}") - h2 {{ template.name }} - p {{ template.description }} - - fieldset - a.button-next.button-green( - href="#" - title="{{'PAGINATION.NEXT' | translate}}" - translate="PAGINATION.NEXT" - ) - - section.wizard-step.create-step2.active(data-step="step2") - div.title - h1(translate="WIZARD.SECTION_TITLE_CREATE_PROJECT") - p(translate="WIZARD.CREATE_PROJECT_TEXT") - div.template-wrapper - div.template-inner - fieldset - input( - type="text" - name="name" - ng-model="data.name" - data-required="true" - placeholder="{{'COMMON.FIELDS.NAME' | translate}}" - maxlength="45" - ) - fieldset - textarea( - name="description" - ng-model="data.description" - data-required="true" - ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" - ) - fieldset.wizard-action - div - a.button-prev.button.button-gray( - href="#" - title="{{'PAGINATION.PREVIOUS' | translate}}" - translate="PAGINATION.PREVIOUS" - ) - button.button-green.submit-button( - type="submit" - title="{{'COMMON.CREATE' | translate}}" - translate="COMMON.CREATE" - ) - - button.hidden(type="submit") - -div.progress-bar - div.progress-state - span(translate="WIZARD.PROGRESS_TEMPLATE_SELECTION") - span(translate="WIZARD.PROGRESS_NAME_DESCRIPTION") - // span Final touches - div.progress-bar-wrapper - div.bar - -a.close(href="#" title="{{'COMMON.CLOSE' | translate}}") - svg.icon.icon-delete - use(xlink:href="#icon-delete") + header + h1.title(translate="WIZARD.SECTION_TITLE_CREATE_PROJECT") + .subtitle( + translate="WIZARD.CREATE_PROJECT_TEXT" + role="presentation" + ) + section.template-option + .template-selector-title + legend(translate="WIZARD.CHOOSE_TEMPLATE") + // UX issue + //- a.more-info( + //- href="" + //- title="{{ 'WIZARD.CHOOSE_TEMPLATE_TITLE' | translate }}" + //- translate="WIZARD.CHOOSE_TEMPLATE_INFO" + //- ) + .template-selector + fieldset(ng-repeat="template in templates") + input( + type="radio" + name="template" + id="template-{{ template.id }}" + ng-value='template.id' + ng-model="data.creation_template" + data-required="true" + ) + label.template-label(for="template-{{ template.id }}") + svg.icon(ng-class="'icon-'+template.slug") + use(xlink:href="{{'#icon-' + template.slug }}") + span.template-name {{ template.name }} + .template-data + legend(translate="WIZARD.PROJECT_DETAILS") + fieldset + input( + type="text" + name="name" + ng-model="data.name" + data-required="true" + placeholder="{{'COMMON.FIELDS.NAME' | translate}}" + maxlength="45" + aria-hidden="true" + ) + fieldset + textarea( + name="description" + ng-model="data.description" + data-required="true" + ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" + ) + .template-privacity + fieldset + input( + type="radio" + name="is_private" + id="template-public" + data-required="true" + aria-hidden="true" + ng-value="false" + ng-model="data.is_private" + required + ng-disabled="!canCreatePublicProjects.valid" + ng-checked="canCreatePublicProjects.valid" + ) + label.template-privacity(for="template-public") + svg.icon.icon-discover + use(xlink:href="#icon-discover") + span(translate="WIZARD.PUBLIC_PROJECT") + fieldset + input( + type="radio" + name="is_private" + id="template-private" + data-required="true" + ng-value="true" + ng-model="data.is_private" + aria-hidden="true" + required + ng-disabled="!canCreatePrivateProjects.valid" + ng-checked="!canCreatePublicProjects.valid" + ) + label.template-privacity(for="template-private") + svg.icon.icon-lock + use(xlink:href="#icon-lock") + span(translate="WIZARD.PRIVATE_PROJECT") + + tg-create-project-restriction + + button.button-green.submit-button( + translate="WIZARD.CREATE_PROJECT" + title="{{'WIZARD.CREATE_PROJECT' | translate}}" + ng-click="" + ) diff --git a/app/partials/project/wizard-restrictions.jade b/app/partials/project/wizard-restrictions.jade new file mode 100644 index 00000000..798bf1fd --- /dev/null +++ b/app/partials/project/wizard-restrictions.jade @@ -0,0 +1,9 @@ +div.create-warning(ng-if="!canCreatePrivateProjects.valid && canCreatePrivateProjects.reason == 'max_private_projects'") + svg.icon.icon-exclamation + use(xlink:href="#icon-exclamation") + span {{ 'WIZARD.MAX_PRIVATE_PROJECTS' | translate }} + +div.create-warning(ng-if="!canCreatePublicProjects.valid && canCreatePublicProjects.reason == 'max_public_projects'") + svg.icon.icon-exclamation + use(xlink:href="#icon-exclamation") + span {{ 'WIZARD.MAX_PUBLIC_PROJECTS' | translate }} diff --git a/app/styles/modules/common/wizard.scss b/app/styles/modules/common/wizard.scss index 494c9048..81a1b6c0 100644 --- a/app/styles/modules/common/wizard.scss +++ b/app/styles/modules/common/wizard.scss @@ -1,73 +1,109 @@ .wizard-create-project { @extend %lightbox; - @extend %background-taiga; - background-size: cover; - color: $white; - text-align: center; - form { - width: 500px; - } - .title { - width: 100%; - } - h1, - p { - color: $white; - } - h1 { - line-height: 1.5rem; - } - p { - @extend %small; - opacity: .8; - } - input, - textarea, - select { - background: rgba($white, .7); - @include placeholder { - color: $grayer; - } - } .close { - color: $white; - &:hover { - color: $red-light; + @include svg-size(2rem); + } + form { + width: 700px; + } + header { + margin-bottom: 3rem; + .title { + margin-bottom: 0; + } + .subtitle { + @extend %small; + color: $gray-light; + text-align: center; } } - .wizard-step { - animation: formSlide .4s ease-in-out; - animation-direction: alternate-reverse; - display: none; - &.active { - animation: formSlide .4s ease-in-out; - &.create-step2, - &.create-step3, - &.create-step1 { - display: block; - } - } + .more-info { + @extend %small; + color: $primary; } - .wizard-action { - div { - display: flex; + .template-selector-title { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + } + .template-selector { + display: flex; + margin-bottom: 1rem; + input { + display: none; } - a { - color: $white; - display: inline-block; - flex-basis: 40%; - flex-grow: 1; + fieldset { &:first-child { margin-right: .5rem; } } } - .create-step1 { - .template-inner { - display: flex; + input { + &:checked+label { + background: $primary-light; + color: $white; + transition: background .2s ease-in; + &:hover { + background: $primary-light; + } } + +label { + background: rgba($whitish, .7); + cursor: pointer; + display: block; + padding: 2rem 1rem; + text-align: center; + transition: background .2s ease-in; + &:hover { + background: rgba($primary-light, .3); + transition: background .2s ease-in; + } + .icon { + @include svg-size(1.4rem); + fill: currentColor; + margin-right: 1rem; + vertical-align: text-top; + } + .template-name { + @extend %large; + text-transform: uppercase; + } + } + } + input[disabled]+label { + background: lighten($whitish, 5%); + box-shadow: none; + color: lighten($gray-light, 20%); + cursor: not-allowed; + opacity: .65; + &:hover { + background: lighten($whitish, 5%); + color: lighten($gray-light, 20%); + } + } + .template-data { + legend { + display: block; + margin-bottom: .5rem; + } + input, + textarea { + background: none; + border: 1px solid $whitish; + color: $gray-light; + @include placeholder { + color: darken($whitish, 20%); + } + } + textarea { + height: 7rem; + min-height: 7rem; + } + } + .template-privacity { + display: flex; fieldset { - flex-grow: 1; + margin-bottom: 0; &:first-child { margin-right: .5rem; } @@ -75,105 +111,37 @@ input { display: none; } - input:checked { - +label { - background: rgba($primary-light, .7); - transition: background .3s ease-in; - } + label { + display: block; + text-align: center; + text-transform: uppercase; + } input+label { - background: rgba($whitish, .7); - cursor: pointer; - display: block; - margin-bottom: 1rem; padding: 1rem; - text-align: center; - transition: background .3s ease-in; - &:hover { - background: rgba($primary, .7); - transition: background .3s ease-in; - } - .icon { - @include svg-size(3rem); - fill: currentColor; - } } - h2 { - color: $white; - margin: 0; - margin-top: .5rem; - text-transform: uppercase; - } - p { - text-align: center; + svg { + @include svg-size(.7rem); } } - .progress-bar { - bottom: 0; - height: .5rem; - left: 0; - position: absolute; - width: 100%; - } - .step1 { - .bar { - transition: width .6s ease-in-out; - width: 25%; + .create-warning { + @extend %small; + padding: 1rem; + text-align: center; + .icon-exclamation { + fill: $red-light; + margin-right: .5rem; + vertical-align: middle; } - .progress-state { - span:nth-child(1) { - color: rgba($white, 1); - transition: color .3s ease-in-out; - transition-delay: .6s; - } - } - } - .step2 { - .bar { - transition: width .6s ease-in-out; - // width: 50%; - width: 75%; - } - .progress-state { - span:nth-child(1), - span:nth-child(2) { - color: rgba($white, 1); - transition: color .3s ease-in-out; - transition-delay: .6s; - } - } - } - - .progress-state { - position: absolute; - width: 100%; - span { - color: rgba($white, .5); + a { + color: $primary; display: inline-block; - margin-left: -100px; - position: absolute; - text-align: center; - top: -2rem; - transition: all 1s ease-in; - width: 200px; - &:nth-child(1) { - left: 25%; - } - &:nth-child(2) { - left: 75%; - } + margin-left: .25rem; } } - .progress-bar-wrapper { - background: rgba($white, .3); - height: .5rem; - } - .bar { - background: rgba($primary-light, .9); - height: .5rem; - left: 0; - position: absolute; - top: 0; - width: 0; + .button-green { + display: block; + margin: 1rem 5rem; + width: calc(100% - 10rem); } } diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index 2a25e0c5..79330287 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -413,10 +413,10 @@ fill="#9dce0a" d="M512.053 24.761l-130.433 173.609-213.936-30.392 30.427 214.125-173.349 130.201 173.349 130.2-30.427 214.123 214.747-30.506 130.064 173.118 129.997-173.027 214.355 30.451-30.588-215.258 172.981-129.922-172.497-129.561 30.458-214.345-214.912 30.53-130.235-173.346z"> - - team-question + + Exclamation + d="M512 0c-282.394 0-512 229.606-512 512s229.606 512 512 512 512-229.606 512-512-229.606-512-512-512zM512 64c247.803 0 448 200.197 448 448s-200.197 448-448 448-448-200.197-448-448 200.197-448 448-448zM275.256 230.001l-45.256 45.251 236.744 236.749-236.744 236.744 45.256 45.256 236.744-236.744 236.744 236.744 45.256-45.256-236.744-236.744 236.744-236.749-45.253-45.251-236.749 236.744z"> Blocked Project