Merge pull request #723 from dod-ccpo/add-portfolio-user
Add portfolio user
This commit is contained in:
commit
a1faa058bd
@ -84,7 +84,7 @@ def make_flask_callbacks(app):
|
|||||||
def _set_globals():
|
def _set_globals():
|
||||||
g.current_user = None
|
g.current_user = None
|
||||||
g.dev = os.getenv("FLASK_ENV", "dev") == "dev"
|
g.dev = os.getenv("FLASK_ENV", "dev") == "dev"
|
||||||
g.matchesPath = lambda href: re.match("^" + href, request.path)
|
g.matchesPath = lambda href: re.search(href, request.full_path)
|
||||||
g.modal = request.args.get("modal", None)
|
g.modal = request.args.get("modal", None)
|
||||||
g.Authorization = Authorization
|
g.Authorization = Authorization
|
||||||
g.Permissions = Permissions
|
g.Permissions = Permissions
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from wtforms.fields import StringField, FormField, FieldList
|
from wtforms.fields import StringField, FormField, FieldList
|
||||||
from wtforms.fields.html5 import EmailField
|
from wtforms.fields.html5 import EmailField, TelField
|
||||||
from wtforms.validators import Required, Email, Length
|
from wtforms.validators import Required, Email, Length, Optional
|
||||||
|
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
from .forms import BaseForm
|
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.forms.fields import SelectField
|
||||||
from atst.utils.localization import translate
|
from atst.utils.localization import translate
|
||||||
|
|
||||||
@ -71,6 +71,10 @@ class NewForm(PermissionsForm):
|
|||||||
email = EmailField(
|
email = EmailField(
|
||||||
translate("forms.new_member.email_label"), validators=[Required(), Email()]
|
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(
|
dod_id = StringField(
|
||||||
translate("forms.new_member.dod_id_label"),
|
translate("forms.new_member.dod_id_label"),
|
||||||
validators=[Required(), Length(min=10), IsNumber()],
|
validators=[Required(), Length(min=10), IsNumber()],
|
||||||
|
@ -8,7 +8,7 @@ from atst.domain.portfolios import Portfolios
|
|||||||
from atst.domain.audit_log import AuditLog
|
from atst.domain.audit_log import AuditLog
|
||||||
from atst.domain.common import Paginator
|
from atst.domain.common import Paginator
|
||||||
from atst.forms.portfolio import PortfolioForm
|
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.models.permissions import Permissions
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
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]
|
members_data = [serialize_member_form_data(member) for member in portfolio.members]
|
||||||
|
|
||||||
portfolio_form = PortfolioForm(data={"name": portfolio.name})
|
portfolio_form = PortfolioForm(data={"name": portfolio.name})
|
||||||
member_perms_form = MembersPermissionsForm(
|
member_perms_form = member_forms.MembersPermissionsForm(
|
||||||
data={"members_permissions": members_data}
|
data={"members_permissions": members_data}
|
||||||
)
|
)
|
||||||
return render_template(
|
return render_template(
|
||||||
@ -71,6 +71,7 @@ def render_admin_page(portfolio, form=None):
|
|||||||
form=form,
|
form=form,
|
||||||
portfolio_form=portfolio_form,
|
portfolio_form=portfolio_form,
|
||||||
member_perms_form=member_perms_form,
|
member_perms_form=member_perms_form,
|
||||||
|
member_form=member_forms.NewForm(),
|
||||||
portfolio=portfolio,
|
portfolio=portfolio,
|
||||||
audit_events=audit_events,
|
audit_events=audit_events,
|
||||||
user=g.current_user,
|
user=g.current_user,
|
||||||
|
@ -92,7 +92,12 @@ def create_member(portfolio_id):
|
|||||||
flash("new_portfolio_member", new_member=member, portfolio=portfolio)
|
flash("new_portfolio_member", new_member=member, portfolio=portfolio)
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for("portfolios.portfolio_members", portfolio_id=portfolio.id)
|
url_for(
|
||||||
|
"portfolios.portfolio_admin",
|
||||||
|
portfolio_id=portfolio.id,
|
||||||
|
fragment="portfolio-members",
|
||||||
|
_anchor="portfolio-members",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
except AlreadyExistsError:
|
except AlreadyExistsError:
|
||||||
return render_template(
|
return render_template(
|
||||||
|
@ -22,10 +22,9 @@ MESSAGES = {
|
|||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
"new_portfolio_member": {
|
"new_portfolio_member": {
|
||||||
"title_template": "Member added successfully",
|
"title_template": "Success!",
|
||||||
"message_template": """
|
"message_template": """
|
||||||
<p>{{ new_member.user_name }} was successfully invited via email to this portfolio. They do not yet have access to any environments.</p>
|
<p>You have successfully invited {{ new_member.user_name }} to the portfolio admin.</p>
|
||||||
<p><a href="{{ url_for('portfolios.update_member', portfolio_id=portfolio.id, member_id=new_member.user_id) }}">Add environment access.</a></p>
|
|
||||||
""",
|
""",
|
||||||
"category": "success",
|
"category": "success",
|
||||||
},
|
},
|
||||||
|
82
js/components/forms/multi_step_modal_form.js
Normal file
82
js/components/forms/multi_step_modal_form.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
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: {
|
||||||
|
steps: Number,
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
step: 0,
|
||||||
|
fields: {},
|
||||||
|
invalid: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.$root.$on('field-mount', this.handleFieldMount)
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function() {
|
||||||
|
this.$root.$on('field-change', this.handleValidChange)
|
||||||
|
this.$on('modalOpen', this.handleModalOpen)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
next: function() {
|
||||||
|
if (this._checkIsValid()) {
|
||||||
|
this.step += 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goToStep: function(step) {
|
||||||
|
if (this._checkIsValid()) {
|
||||||
|
this.step = step
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleValidChange: function(event) {
|
||||||
|
const { name, valid } = event
|
||||||
|
this.fields[name] = valid
|
||||||
|
this._checkIsValid()
|
||||||
|
},
|
||||||
|
_checkIsValid: function() {
|
||||||
|
const valid = !Object.values(this.fields).some(field => field === false)
|
||||||
|
this.invalid = !valid
|
||||||
|
return valid
|
||||||
|
},
|
||||||
|
handleFieldMount: function(event) {
|
||||||
|
const { name, optional } = event
|
||||||
|
this.fields[name] = optional
|
||||||
|
},
|
||||||
|
handleModalOpen: function(_bool) {
|
||||||
|
this.step = 0
|
||||||
|
},
|
||||||
|
_onLastPage: function() {
|
||||||
|
return this.step === this.steps - 1
|
||||||
|
},
|
||||||
|
handleSubmit: function(e) {
|
||||||
|
if (this.invalid || !this._onLastPage()) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.next()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {},
|
||||||
|
}
|
@ -25,6 +25,7 @@ export default {
|
|||||||
},
|
},
|
||||||
paragraph: String,
|
paragraph: String,
|
||||||
noMaxWidth: String,
|
noMaxWidth: String,
|
||||||
|
optional: Boolean,
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
@ -64,6 +65,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
created: function() {
|
||||||
|
this.$root.$emit('field-mount', {
|
||||||
|
name: this.name,
|
||||||
|
optional: this.optional,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
// When user types a character
|
// When user types a character
|
||||||
onInput: function(e) {
|
onInput: function(e) {
|
||||||
@ -82,7 +90,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onBlur: function(e) {
|
onBlur: function(e) {
|
||||||
|
if (!(this.optional && e.target.value === '')) {
|
||||||
this._checkIfValid({ value: e.target.value.trim(), invalidate: true })
|
this._checkIfValid({ value: e.target.value.trim(), invalidate: true })
|
||||||
|
}
|
||||||
this.value = e.target.value.trim()
|
this.value = e.target.value.trim()
|
||||||
|
|
||||||
if (this.validation === 'dollars') {
|
if (this.validation === 'dollars') {
|
||||||
@ -97,6 +107,8 @@ export default {
|
|||||||
|
|
||||||
if (!this.modified && this.initialErrors && this.initialErrors.length) {
|
if (!this.modified && this.initialErrors && this.initialErrors.length) {
|
||||||
valid = false
|
valid = false
|
||||||
|
} else if (this.optional && value === '') {
|
||||||
|
valid = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.modified) {
|
if (this.modified) {
|
||||||
|
@ -18,6 +18,7 @@ import toggler from './components/toggler'
|
|||||||
import NewApplication from './components/forms/new_application'
|
import NewApplication from './components/forms/new_application'
|
||||||
import EditEnvironmentRole from './components/forms/edit_environment_role'
|
import EditEnvironmentRole from './components/forms/edit_environment_role'
|
||||||
import EditApplicationRoles from './components/forms/edit_application_roles'
|
import EditApplicationRoles from './components/forms/edit_application_roles'
|
||||||
|
import MultiStepModalForm from './components/forms/multi_step_modal_form'
|
||||||
import funding from './components/forms/funding'
|
import funding from './components/forms/funding'
|
||||||
import uploadinput from './components/upload_input'
|
import uploadinput from './components/upload_input'
|
||||||
import Modal from './mixins/modal'
|
import Modal from './mixins/modal'
|
||||||
@ -59,6 +60,7 @@ const app = new Vue({
|
|||||||
LocalDatetime,
|
LocalDatetime,
|
||||||
EditEnvironmentRole,
|
EditEnvironmentRole,
|
||||||
EditApplicationRoles,
|
EditApplicationRoles,
|
||||||
|
MultiStepModalForm,
|
||||||
ConfirmationPopover,
|
ConfirmationPopover,
|
||||||
funding,
|
funding,
|
||||||
uploadinput,
|
uploadinput,
|
||||||
|
1
static/icons/checkmark-alt.svg
Normal file
1
static/icons/checkmark-alt.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><path d="M434.539,98.499l-38.828-38.828c-5.324-5.328-11.799-7.993-19.41-7.993c-7.618,0-14.093,2.665-19.417,7.993L169.59,247.248 l-83.939-84.225c-5.33-5.33-11.801-7.992-19.412-7.992c-7.616,0-14.087,2.662-19.417,7.992L7.994,201.852 C2.664,207.181,0,213.654,0,221.269c0,7.609,2.664,14.088,7.994,19.416l103.351,103.349l38.831,38.828 c5.327,5.332,11.8,7.994,19.414,7.994c7.611,0,14.084-2.669,19.414-7.994l38.83-38.828L434.539,137.33 c5.325-5.33,7.994-11.802,7.994-19.417C442.537,110.302,439.864,103.829,434.539,98.499z" fill="#fff" fill-rule="nonzero"/></svg>
|
After Width: | Height: | Size: 617 B |
@ -139,4 +139,81 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.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;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
|
&[type='button']:disabled {
|
||||||
|
background-color: $color-gray-lighter;
|
||||||
|
opacity: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal__form--padded {
|
||||||
|
padding-left: 5%;
|
||||||
|
padding-right: 5%;
|
||||||
|
|
||||||
|
.usa-input .usa-input__choices select {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,6 +197,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
th:first-child {
|
th:first-child {
|
||||||
padding-left: 3 * $gap;
|
padding-left: 3 * $gap;
|
||||||
@ -294,6 +295,25 @@
|
|||||||
float: right;
|
float: right;
|
||||||
padding: 3 * $gap;
|
padding: 3 * $gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.modal-link.icon-link {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.7rem;
|
||||||
|
height: 1.7rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.7rem;
|
||||||
|
height: 1.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin: 4rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-content {
|
.application-content {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
{% macro Modal(name, dismissable=False) -%}
|
{% macro Modal(name, dismissable=False, classes="") -%}
|
||||||
<div v-show="activeModal === '{{name}}'" v-cloak>
|
<div v-show="activeModal === '{{name}}'" v-cloak>
|
||||||
<div id='modal--{{name}}' class='modal {% if dismissable %}modal--dismissable{% endif%}'>
|
<div id='modal--{{name}}' class='modal {% if dismissable %}modal--dismissable{% endif%} {{ classes }}'>
|
||||||
<div class='modal__container'>
|
<div class='modal__container'>
|
||||||
<div class='modal__dialog' role='dialog' aria-modal='true'>
|
<div class='modal__dialog' role='dialog' aria-modal='true'>
|
||||||
<div class='modal__body'>
|
<div class='modal__body'>
|
||||||
|
51
templates/components/multi_step_modal_form.html
Normal file
51
templates/components/multi_step_modal_form.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% from "components/modal.html" import Modal %}
|
||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
|
{% set numbers = ['one', 'two', 'three', 'four', 'five'] %}
|
||||||
|
|
||||||
|
{% macro FormSteps(step_count, current_step) -%}
|
||||||
|
{% set count = numbers[step_count - 1] %}
|
||||||
|
<div class="progress-menu progress-menu--{{ count }}">
|
||||||
|
<ul>
|
||||||
|
{% for step in range(step_count) %}
|
||||||
|
<li class="progress-menu__item
|
||||||
|
{% if loop.index < current_step %}
|
||||||
|
progress-menu__item--complete
|
||||||
|
{% elif loop.index == current_step %}
|
||||||
|
progress-menu__item--active
|
||||||
|
{% else %}
|
||||||
|
progress-menu__item--incomplete
|
||||||
|
{% endif %}">
|
||||||
|
<a v-on:click="goToStep({{ step }})">
|
||||||
|
Step {{ loop.index }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro MultiStepModalForm(name, form, form_action, steps, button_text="", dismissable=False) -%}
|
||||||
|
{% set step_count = steps|length %}
|
||||||
|
<multi-step-modal-form inline-template :steps={{ step_count }}>
|
||||||
|
<div>
|
||||||
|
<a class='icon-link modal-link' v-on:click="openModal('{{ name }}')">
|
||||||
|
{{ button_text }}
|
||||||
|
{{ Icon('plus-circle-solid') }}
|
||||||
|
</a>
|
||||||
|
{% call Modal(name=name, dismissable=dismissable, classes="wide") %}
|
||||||
|
<form id="{{ name }}" action="{{ form_action }}" method="POST" v-on:submit="handleSubmit">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
<div v-if="activeModal === '{{ name }}'">
|
||||||
|
{% for step in steps %}
|
||||||
|
<div class="modal__form" v-show="step === {{ loop.index0 }}">
|
||||||
|
{{ FormSteps(step_count, loop.index) }}
|
||||||
|
{{ step }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endcall %}
|
||||||
|
</div>
|
||||||
|
</multi-step-modal-form>
|
||||||
|
{% endmacro %}
|
@ -7,7 +7,8 @@
|
|||||||
inline-template
|
inline-template
|
||||||
{% if field.errors %}v-bind:initial-errors='{{ field.errors | list }}'{% endif %}
|
{% if field.errors %}v-bind:initial-errors='{{ field.errors | list }}'{% endif %}
|
||||||
{% if field.data and field.data != "None" %}v-bind:initial-value="'{{ field.data }}'"{% endif %}
|
{% if field.data and field.data != "None" %}v-bind:initial-value="'{{ field.data }}'"{% endif %}
|
||||||
key='{{ field.name }}'>
|
key='{{ field.name }}'
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-bind:class="['usa-input', { 'usa-input--error': showError, 'usa-input--success': showValid }]">
|
v-bind:class="['usa-input', { 'usa-input--error': showError, 'usa-input--success': showValid }]">
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
disabled=False,
|
disabled=False,
|
||||||
initial_value='',
|
initial_value='',
|
||||||
classes='',
|
classes='',
|
||||||
noMaxWidth=False) -%}
|
noMaxWidth=False,
|
||||||
|
optional=False) -%}
|
||||||
|
|
||||||
<textinput
|
<textinput
|
||||||
v-cloak
|
v-cloak
|
||||||
@ -23,6 +24,7 @@
|
|||||||
{% if noMaxWidth %}no-max-width='true'{% endif %}
|
{% if noMaxWidth %}no-max-width='true'{% endif %}
|
||||||
{% if initial_value or field.data is not none %}initial-value='{{ initial_value or field.data }}'{% endif %}
|
{% if initial_value or field.data is not none %}initial-value='{{ initial_value or field.data }}'{% endif %}
|
||||||
{% if field.errors %}v-bind:initial-errors='{{ field.errors | list }}'{% endif %}
|
{% if field.errors %}v-bind:initial-errors='{{ field.errors | list }}'{% endif %}
|
||||||
|
v-bind:optional={{ optional|lower }}
|
||||||
key='{{ field.name }}'
|
key='{{ field.name }}'
|
||||||
inline-template>
|
inline-template>
|
||||||
|
|
||||||
|
84
templates/fragments/admin/add_new_portfolio_member.html
Normal file
84
templates/fragments/admin/add_new_portfolio_member.html
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||||
|
|
||||||
|
{% macro SimpleOptionsInput(field) %}
|
||||||
|
<div class="usa-input">
|
||||||
|
<fieldset data-ally-disabled="true" class="usa-input__choices">
|
||||||
|
<legend>
|
||||||
|
<div class="usa-input__title-inline">
|
||||||
|
{{ field.label | striptags}}
|
||||||
|
</div>
|
||||||
|
</legend>
|
||||||
|
{{ field() }}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% set step_one %}
|
||||||
|
<div class="modal__form--header">
|
||||||
|
<h1>Invite New Portfolio Member</h1>
|
||||||
|
</div>
|
||||||
|
<div class='form-row'>
|
||||||
|
<div class='form-col form-col--half'>
|
||||||
|
{{ TextInput(member_form.first_name, validation='requiredField') }}
|
||||||
|
</div>
|
||||||
|
<div class='form-col form-col--half'>
|
||||||
|
{{ TextInput(member_form.last_name, validation='requiredField') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='form-row'>
|
||||||
|
<div class='form-col form-col--half'>
|
||||||
|
{{ TextInput(member_form.email, validation='email') }}
|
||||||
|
</div>
|
||||||
|
<div class='form-col form-col--half'>
|
||||||
|
{{ TextInput(member_form.phone_number, validation='usPhone', optional=True) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='form-row'>
|
||||||
|
<div class='form-col form-col--half'>
|
||||||
|
{{ TextInput(member_form.dod_id, validation='dodId') }}
|
||||||
|
</div>
|
||||||
|
<div class='form-col form-col--half'>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='action-group'>
|
||||||
|
<input
|
||||||
|
type='button'
|
||||||
|
v-on:click="next()"
|
||||||
|
v-bind:disabled="invalid"
|
||||||
|
class='action-group__action usa-button'
|
||||||
|
value='Next Step'>
|
||||||
|
<a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('{{ new_port_mem }}')">Cancel</a>
|
||||||
|
</div>
|
||||||
|
{% endset %}
|
||||||
|
{% set step_two %}
|
||||||
|
<div class="modal__form--padded">
|
||||||
|
<div class="modal__form--header">
|
||||||
|
<h1>Assign Member Permissions</h1>
|
||||||
|
<a class='icon-link'>
|
||||||
|
{{ Icon('info') }}
|
||||||
|
{{ "portfolios.admin.permissions_info" | translate }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ SimpleOptionsInput(member_form.perms_app_mgmt) }}
|
||||||
|
{{ SimpleOptionsInput(member_form.perms_funding) }}
|
||||||
|
{{ SimpleOptionsInput(member_form.perms_reporting) }}
|
||||||
|
{{ SimpleOptionsInput(member_form.perms_portfolio_mgmt) }}
|
||||||
|
<div class='action-group'>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
class='action-group__action usa-button'
|
||||||
|
form="add-port-mem"
|
||||||
|
value='Invite Member'>
|
||||||
|
<a class='action-group__action icon-link icon-link--default' v-on:click="closeModal('{{ new_port_mem }}')">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endset %}
|
||||||
|
{{ MultiStepModalForm(
|
||||||
|
'add-port-mem',
|
||||||
|
member_form,
|
||||||
|
url_for("portfolios.create_member", portfolio_id=portfolio.id),
|
||||||
|
[step_one, step_two],
|
||||||
|
button_text="portfolios.admin.add_new_member" | translate)
|
||||||
|
}}
|
@ -1,10 +1,12 @@
|
|||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/options_input.html" import OptionsInput %}
|
{% from "components/options_input.html" import OptionsInput %}
|
||||||
|
|
||||||
<section class="member-list">
|
<section class="member-list" id="portfolio-members">
|
||||||
<div class='responsive-table-wrapper panel'>
|
<div class='responsive-table-wrapper panel'>
|
||||||
<form method='POST' autocomplete="off" enctype="multipart/form-data">
|
{% if g.matchesPath("portfolio-members") %}
|
||||||
|
{% include "fragments/flash.html" %}
|
||||||
|
{% endif %}
|
||||||
|
<form method='POST' id="member-perms" autocomplete="off" enctype="multipart/form-data">
|
||||||
<div class='member-list-header'>
|
<div class='member-list-header'>
|
||||||
<div class='left'>
|
<div class='left'>
|
||||||
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
<div class='h3'>{{ "portfolios.admin.portfolio_members_title" | translate }}</div>
|
||||||
@ -13,17 +15,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class='icon-link'>
|
<a class='icon-link'>
|
||||||
<span class='icon'>{{ Icon('info') }}</span>
|
{{ Icon('info') }}
|
||||||
{{ "portfolios.admin.settings_info" | translate }}
|
{{ "portfolios.admin.settings_info" | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not portfolio.members %}
|
{% if not portfolio.members %}
|
||||||
|
|
||||||
<p>There are currently no members in this Portfolio.</p>
|
<p>There are currently no members in this Portfolio.</p>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
@ -47,19 +46,24 @@
|
|||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
|
||||||
<div class="members-table-footer">
|
|
||||||
<a class='icon-link'>
|
|
||||||
{{ "portfolios.admin.add_member" | translate }}
|
|
||||||
{{ Icon('plus-circle-solid') }}
|
|
||||||
</a>
|
|
||||||
<input type='submit' class='usa-button usa-button-primary' value='{{ "Save" }}' />
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
<div class="members-table-footer">
|
||||||
|
<div class="action-group">
|
||||||
|
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||||
|
<input
|
||||||
|
type='submit'
|
||||||
|
form="member-perms"
|
||||||
|
class='usa-button usa-button-primary'
|
||||||
|
value='Save' />
|
||||||
|
{% endif %}
|
||||||
|
{% if user_can(permissions.CREATE_PORTFOLIO_USERS) %}
|
||||||
|
{% include "fragments/admin/add_new_portfolio_member.html" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
@ -3,13 +3,12 @@
|
|||||||
{% from "components/pagination.html" import Pagination %}
|
{% from "components/pagination.html" import Pagination %}
|
||||||
{% from "components/icon.html" import Icon %}
|
{% from "components/icon.html" import Icon %}
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
|
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
|
||||||
|
|
||||||
{% set secondary_breadcrumb = "navigation.portfolio_navigation.portfolio_admin" | translate %}
|
{% set secondary_breadcrumb = "navigation.portfolio_navigation.portfolio_admin" | translate %}
|
||||||
|
|
||||||
{% block portfolio_content %}
|
{% block portfolio_content %}
|
||||||
|
|
||||||
{% include "fragments/flash.html" %}
|
|
||||||
|
|
||||||
<div v-cloak class="portfolio-admin portfolio-content">
|
<div v-cloak class="portfolio-admin portfolio-content">
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
|
@ -134,6 +134,7 @@ forms:
|
|||||||
new_member:
|
new_member:
|
||||||
dod_id_label: DOD ID
|
dod_id_label: DOD ID
|
||||||
email_label: Email Address
|
email_label: Email Address
|
||||||
|
phone_number_label: Phone Number (Optional)
|
||||||
first_name_label: First Name
|
first_name_label: First Name
|
||||||
last_name_label: Last 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.'
|
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,7 +569,9 @@ portfolios:
|
|||||||
portfolio_members_subheading: These members have different levels of access to the portfolio.
|
portfolio_members_subheading: These members have different levels of access to the portfolio.
|
||||||
settings_info: Learn more about these settings
|
settings_info: Learn more about these settings
|
||||||
add_member: Add a New Member
|
add_member: Add a New Member
|
||||||
|
permissions_info: Learn more about these permissions
|
||||||
activity_log_title: Activity Log
|
activity_log_title: Activity Log
|
||||||
|
add_new_member: Add a New Member
|
||||||
members:
|
members:
|
||||||
archive_button: Archive User
|
archive_button: Archive User
|
||||||
permissions:
|
permissions:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user