From 1bc434be8cbd394173736e7fde0d33c7998e5b71 Mon Sep 17 00:00:00 2001 From: dandds Date: Fri, 22 Mar 2019 13:36:21 -0400 Subject: [PATCH 01/13] multi-step form modal with basic implementation for adding new member --- atst/forms/portfolio_member.py | 10 +++- atst/routes/portfolios/index.py | 5 +- js/components/forms/multi_step_modal_form.js | 43 ++++++++++++++ js/index.js | 2 + .../components/multi_step_modal_form.html | 45 +++++++++++++++ templates/portfolios/admin.html | 57 +++++++++++++++++++ translations.yaml | 2 + 7 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 js/components/forms/multi_step_modal_form.js create mode 100644 templates/components/multi_step_modal_form.html diff --git a/atst/forms/portfolio_member.py b/atst/forms/portfolio_member.py index 492777a7..2293deed 100644 --- a/atst/forms/portfolio_member.py +++ b/atst/forms/portfolio_member.py @@ -1,10 +1,10 @@ from wtforms.fields import StringField, FormField, FieldList -from wtforms.fields.html5 import EmailField -from wtforms.validators import Required, Email, Length +from wtforms.fields.html5 import EmailField, TelField +from wtforms.validators import Required, Email, Length, Optional from atst.domain.permission_sets import PermissionSets from .forms import BaseForm -from atst.forms.validators import IsNumber +from atst.forms.validators import IsNumber, PhoneNumber from atst.forms.fields import SelectField from atst.utils.localization import translate @@ -71,6 +71,10 @@ class NewForm(PermissionsForm): email = EmailField( translate("forms.new_member.email_label"), validators=[Required(), Email()] ) + phone_number = TelField( + translate("forms.new_member.phone_number_label"), + validators=[Optional(), PhoneNumber()], + ) dod_id = StringField( translate("forms.new_member.dod_id_label"), validators=[Required(), Length(min=10), IsNumber()], diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index b87763d4..78f0ba68 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -8,7 +8,7 @@ from atst.domain.portfolios import Portfolios from atst.domain.audit_log import AuditLog from atst.domain.common import Paginator from atst.forms.portfolio import PortfolioForm -from atst.forms.portfolio_member import MembersPermissionsForm +import atst.forms.portfolio_member as member_forms from atst.models.permissions import Permissions from atst.domain.permission_sets import PermissionSets from atst.domain.authz.decorator import user_can_access_decorator as user_can @@ -63,7 +63,7 @@ def render_admin_page(portfolio, form=None): members_data = [serialize_member_form_data(member) for member in portfolio.members] portfolio_form = PortfolioForm(data={"name": portfolio.name}) - member_perms_form = MembersPermissionsForm( + member_perms_form = member_forms.MembersPermissionsForm( data={"members_permissions": members_data} ) return render_template( @@ -71,6 +71,7 @@ def render_admin_page(portfolio, form=None): form=form, portfolio_form=portfolio_form, member_perms_form=member_perms_form, + member_form=member_forms.NewForm(), portfolio=portfolio, audit_events=audit_events, user=g.current_user, diff --git a/js/components/forms/multi_step_modal_form.js b/js/components/forms/multi_step_modal_form.js new file mode 100644 index 00000000..0fa2f0d0 --- /dev/null +++ b/js/components/forms/multi_step_modal_form.js @@ -0,0 +1,43 @@ +import FormMixin from '../../mixins/form' +import textinput from '../text_input' +import optionsinput from '../options_input' +import Selector from '../selector' +import Modal from '../../mixins/modal' +import toggler from '../toggler' + +export default { + name: 'multi-step-modal-form', + + mixins: [FormMixin, Modal], + + components: { + toggler, + Modal, + Selector, + textinput, + optionsinput, + }, + + props: {}, + + data: function() { + return { + step: 0, + } + }, + + mounted: function() { + return {} + }, + + methods: { + next: function() { + this.step += 1 + }, + goToStep: function(step) { + this.step = step + }, + }, + + computed: {}, +} diff --git a/js/index.js b/js/index.js index 654be95c..a5e58f45 100644 --- a/js/index.js +++ b/js/index.js @@ -18,6 +18,7 @@ import toggler from './components/toggler' import NewApplication from './components/forms/new_application' import EditEnvironmentRole from './components/forms/edit_environment_role' import EditApplicationRoles from './components/forms/edit_application_roles' +import MultiStepModalForm from './components/forms/multi_step_modal_form' import funding from './components/forms/funding' import uploadinput from './components/upload_input' import Modal from './mixins/modal' @@ -59,6 +60,7 @@ const app = new Vue({ LocalDatetime, EditEnvironmentRole, EditApplicationRoles, + MultiStepModalForm, ConfirmationPopover, funding, uploadinput, diff --git a/templates/components/multi_step_modal_form.html b/templates/components/multi_step_modal_form.html new file mode 100644 index 00000000..f31323b8 --- /dev/null +++ b/templates/components/multi_step_modal_form.html @@ -0,0 +1,45 @@ +{% from "components/modal.html" import Modal %} + +{% set numbers = ['one', 'two', 'three', 'four', 'five'] %} + +{% macro FormSteps(step_count, current_step) -%} + {% set count = numbers[step_count - 1] %} +
+ +
+{% endmacro %} + +{% macro MultiStepModalForm(name, form, form_action, steps, button_text="", dismissable=False) -%} + {% set step_count = steps|length %} + +
+ +
+ {{ form.csrf_token }} + {% call Modal(name=name, dismissable=dismissable) %} + {% for step in steps %} +
+ {{ FormSteps(step_count, loop.index) }} + {{ step }} +
+ {% endfor %} + {% endcall %} +
+
+
+{% endmacro %} diff --git a/templates/portfolios/admin.html b/templates/portfolios/admin.html index 2f0614a8..b8cc7942 100644 --- a/templates/portfolios/admin.html +++ b/templates/portfolios/admin.html @@ -3,6 +3,8 @@ {% from "components/pagination.html" import Pagination %} {% from "components/icon.html" import Icon %} {% from "components/text_input.html" import TextInput %} +{% from "components/multi_step_modal_form.html" import MultiStepModalForm %} +{% from "components/options_input.html" import OptionsInput %} {% set secondary_breadcrumb = "navigation.portfolio_navigation.portfolio_admin" | translate %} @@ -50,6 +52,61 @@ {% include "fragments/admin/portfolio_members.html" %} {% endif %} + {% set step_one %} +

Invite New Portfolio Member

+
+
+ {{ TextInput(member_form.first_name, validation='requiredField') }} +
+
+ {{ TextInput(member_form.last_name, validation='requiredField') }} +
+
+
+
+ {{ TextInput(member_form.email, validation='email') }} +
+
+ {{ TextInput(member_form.phone_number, validation='usPhone') }} +
+
+
+
+ {{ TextInput(member_form.dod_id, validation='dodId') }} +
+
+
+
+
+ Next Step + Cancel +
+ {% endset %} + {% set step_two %} +

Assign Member Permissions

+ + {{ Icon('info') }} + {{ "portfolios.admin.permissions_info" | translate }} + + {{ OptionsInput(member_form.perms_app_mgmt) }} + {{ OptionsInput(member_form.perms_funding) }} + {{ OptionsInput(member_form.perms_reporting) }} + {{ OptionsInput(member_form.perms_portfolio_mgmt) }} +
+ + Cancel +
+ {% endset %} + {{ MultiStepModalForm( + 'add-port-mem', + member_form, + url_for("portfolios.create_member", portfolio_id=portfolio.id), + [step_one, step_two], + button_text="add new member") + }} + + {% include "fragments/audit_events_log.html" %} + {% if user_can(permissions.VIEW_PORTFOLIO_ACTIVITY_LOG) %} {% include "fragments/audit_events_log.html" %} {{ Pagination(audit_events, 'portfolios.portfolio_admin', portfolio_id=portfolio.id) }} diff --git a/translations.yaml b/translations.yaml index 1577f78c..364ced82 100644 --- a/translations.yaml +++ b/translations.yaml @@ -134,6 +134,7 @@ forms: new_member: dod_id_label: DOD ID email_label: Email Address + phone_number_label: Phone Number (Optional) first_name_label: First Name last_name_label: Last Name portfolio_role_description: 'The portfolio role controls whether a member is permitted to organize a portfolio into applications and environments, add members to this portfolio, and view billing information.' @@ -568,6 +569,7 @@ portfolios: portfolio_members_subheading: These members have different levels of access to the portfolio. settings_info: Learn more about these settings add_member: Add a New Member + permissions_info: Learn more about these permissions activity_log_title: Activity Log members: archive_button: Archive User From 0d3387b0fe2fbf963f9402c52c5a4bcf67900a9f Mon Sep 17 00:00:00 2001 From: dandds Date: Sun, 24 Mar 2019 16:10:52 -0400 Subject: [PATCH 02/13] styles for multi-step header in modal form --- static/icons/checkmark-alt.svg | 1 + styles/components/_modal.scss | 19 +++++++++++++++++++ .../components/multi_step_modal_form.html | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 static/icons/checkmark-alt.svg diff --git a/static/icons/checkmark-alt.svg b/static/icons/checkmark-alt.svg new file mode 100644 index 00000000..b674534c --- /dev/null +++ b/static/icons/checkmark-alt.svg @@ -0,0 +1 @@ + diff --git a/styles/components/_modal.scss b/styles/components/_modal.scss index 90015844..db54378f 100644 --- a/styles/components/_modal.scss +++ b/styles/components/_modal.scss @@ -139,4 +139,23 @@ body { } } } + + .modal--form { + .progress-menu ul { + width: 40%; + margin-left: 30%; + font-size: 2rem; + + .progress-menu__item::before { + width: 2.8rem; + height: 2.8rem; + margin-left: -1.25rem; + } + + .progress-menu__item--complete::before { + content: url('#{$asset-path}/icons/checkmark-alt.svg'); + padding-top: 0.4rem; + } + } + } } diff --git a/templates/components/multi_step_modal_form.html b/templates/components/multi_step_modal_form.html index f31323b8..62703344 100644 --- a/templates/components/multi_step_modal_form.html +++ b/templates/components/multi_step_modal_form.html @@ -33,7 +33,7 @@ {{ form.csrf_token }} {% call Modal(name=name, dismissable=dismissable) %} {% for step in steps %} -
+ From ec794abbf26775ae08ab8d742412b2f1e58b8dc6 Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 25 Mar 2019 06:53:48 -0400 Subject: [PATCH 03/13] more styling for new portfolio member modal --- styles/components/_modal.scss | 58 ++++++++++++++++++- templates/components/modal.html | 4 +- .../components/multi_step_modal_form.html | 4 +- templates/components/options_input.html | 3 +- templates/portfolios/admin.html | 48 ++++++++++----- 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/styles/components/_modal.scss b/styles/components/_modal.scss index db54378f..0947fb35 100644 --- a/styles/components/_modal.scss +++ b/styles/components/_modal.scss @@ -140,7 +140,36 @@ body { } } - .modal--form { + &.wide { + .modal__dialog { + max-width: 90rem; + } + + .modal__body { + padding-left: 4rem; + padding-right: 4rem; + } + } + + .modal__form { + + .modal__form--header { + margin-bottom: 4rem; + + h1 { + margin-bottom: 0; + } + + .icon-link { + padding-top: 0.5rem; + padding-left: 0; + + .icon { + margin-left: 0; + } + } + } + .progress-menu ul { width: 40%; margin-left: 30%; @@ -157,5 +186,32 @@ body { padding-top: 0.4rem; } } + + .form-row { + margin-top: 0; + + .form-col { + .usa-input { + margin-bottom: 1.5rem; + } + } + } + + .icon-link--default { + font-size: 1.7rem; + } + + .usa-button { + margin-left: 2rem; + } + + .modal__form--padded { + padding-left: 5%; + padding-right: 5%; + + .usa-input .usa-input__choices select { + max-width: 100%; + } + } } } diff --git a/templates/components/modal.html b/templates/components/modal.html index 9df49137..c7b582ff 100644 --- a/templates/components/modal.html +++ b/templates/components/modal.html @@ -1,8 +1,8 @@ {% from "components/icon.html" import Icon %} -{% macro Modal(name, dismissable=False) -%} +{% macro Modal(name, dismissable=False, classes="") -%}
-