Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2854 - Org Member Invitation - [NL] #3080

Merged
merged 18 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,130 changes: 972 additions & 158 deletions src/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/registrar/assets/src/js/getgov/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { initDomainRequestsTable } from './table-domain-requests.js';
import { initMembersTable } from './table-members.js';
import { initMemberDomainsTable } from './table-member-domains.js';
import { initPortfolioMemberPageToggle } from './portfolio-member-page.js';
import { initAddNewMemberPageListeners } from './portfolio-member-page.js';

initDomainValidators();

Expand Down Expand Up @@ -42,3 +43,4 @@ initMembersTable();
initMemberDomainsTable();

initPortfolioMemberPageToggle();
initAddNewMemberPageListeners();
126 changes: 126 additions & 0 deletions src/registrar/assets/src/js/getgov/portfolio-member-page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { uswdsInitializeModals } from './helpers-uswds.js';
import { getCsrfToken } from './helpers.js';
import { generateKebabHTML } from './table-base.js';
import { MembersTable } from './table-members.js';

Expand Down Expand Up @@ -41,3 +42,128 @@ export function initPortfolioMemberPageToggle() {
}
});
}


/**
* Hooks up specialized listeners for handling form validation and modals
* on the Add New Member page.
*/
export function initAddNewMemberPageListeners() {

document.getElementById("confirm_new_member_submit").addEventListener("click", function() {
// Upon confirmation, submit the form
document.getElementById("add_member_form").submit();
});
CocoByte marked this conversation as resolved.
Show resolved Hide resolved

document.getElementById("add_member_form").addEventListener("submit", function(event) {
event.preventDefault(); // Prevents the form from submitting
const form = document.getElementById("add_member_form")
const formData = new FormData(form);

// Check if the form is valid
// If the form is valid, open the confirmation modal
// If the form is invalid, submit it to trigger error
fetch(form.action, {
method: "POST",
body: formData,
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-CSRFToken": getCsrfToken()
}
})
.then(response => response.json())
.then(data => {
if (data.is_valid) {
// If the form is valid, show the confirmation modal before submitting
openAddMemberConfirmationModal();
} else {
// If the form is not valid, trigger error messages by firing a submit event
form.submit();
}
});
});

/*
Helper function to capitalize the first letter in a string (for display purposes)
*/
function capitalizeFirstLetter(text) {
if (!text) return ''; // Return empty string if input is falsy
return text.charAt(0).toUpperCase() + text.slice(1);
}

/*
Populates contents of the "Add Member" confirmation modal
*/
function populatePermissionDetails(permission_details_div_id) {
const permissionDetailsContainer = document.getElementById("permission_details");
permissionDetailsContainer.innerHTML = ""; // Clear previous content

// Get all permission sections (divs with h3 and radio inputs)
const permissionSections = document.querySelectorAll(`#${permission_details_div_id} > h3`);

permissionSections.forEach(section => {
// Find the <h3> element text
const sectionTitle = section.textContent;

// Find the associated radio buttons container (next fieldset)
const fieldset = section.nextElementSibling;

if (fieldset && fieldset.tagName.toLowerCase() === 'fieldset') {
// Get the selected radio button within this fieldset
const selectedRadio = fieldset.querySelector('input[type="radio"]:checked');

// If a radio button is selected, get its label text
let selectedPermission = "No permission selected";
if (selectedRadio) {
const label = fieldset.querySelector(`label[for="${selectedRadio.id}"]`);
selectedPermission = label ? label.textContent : "No permission selected";
}

// Create new elements for the modal content
const titleElement = document.createElement("h4");
titleElement.textContent = sectionTitle;
titleElement.classList.add("text-primary");
titleElement.classList.add("margin-bottom-0");

const permissionElement = document.createElement("p");
permissionElement.textContent = selectedPermission;
permissionElement.classList.add("margin-top-0");

// Append to the modal content container
permissionDetailsContainer.appendChild(titleElement);
permissionDetailsContainer.appendChild(permissionElement);
}
});
}

/*
Updates and opens the "Add Member" confirmation modal.
*/
function openAddMemberConfirmationModal() {
//------- Populate modal details
// Get email value
let emailValue = document.getElementById('id_email').value;
document.getElementById('modalEmail').textContent = emailValue;

// Get selected radio button for access level
let selectedAccess = document.querySelector('input[name="member_access_level"]:checked');
// Set the selected permission text to 'Basic' or 'Admin' (the value of the selected radio button)
// This value does not have the first letter capitalized so let's capitalize it
let accessText = selectedAccess ? capitalizeFirstLetter(selectedAccess.value) : "No access level selected";
document.getElementById('modalAccessLevel').textContent = accessText;

// Populate permission details based on access level
if (selectedAccess && selectedAccess.value === 'admin') {
populatePermissionDetails('new-member-admin-permissions')
} else {
populatePermissionDetails('new-member-basic-permissions')
}

//------- Show the modal
let modalTrigger = document.querySelector("#invite_member_trigger");
if (modalTrigger) {
modalTrigger.click()
}
}

}
88 changes: 82 additions & 6 deletions src/registrar/templates/portfolio_members_add_new.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ <h1>Add a new member</h1>

{% include "includes/required_fields.html" %}

<form class="usa-form usa-form--large" method="post" novalidate>
<form class="usa-form usa-form--large" method="post" id="add_member_form" novalidate>
CocoByte marked this conversation as resolved.
Show resolved Hide resolved

<fieldset class="usa-fieldset margin-top-2">
<legend>
<h2>Email</h2>
Expand Down Expand Up @@ -80,12 +81,17 @@ <h2>Member Access</h2>
<h2>Admin access permissions</h2>
<p>Member permissions available for admin-level acccess.</p>

<h3 class="margin-bottom-0">Organization domain requests</h3>
<h3 class="summary-item__title
text-primary-dark
margin-bottom-0">Organization domain requests</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
{% input_with_errors form.admin_org_domain_request_permissions %}
{% endwith %}

<h3 class="margin-bottom-0 margin-top-3">Organization members</h3>
<h3 class="summary-item__title
text-primary-dark
margin-bottom-0
margin-top-3">Organization members</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
{% input_with_errors form.admin_org_members_permissions %}
{% endwith %}
Expand All @@ -94,8 +100,12 @@ <h3 class="margin-bottom-0 margin-top-3">Organization members</h3>
<!-- Basic access form -->
<div id="new-member-basic-permissions" class="margin-top-2">
<h2>Basic member permissions</h2>
<p>Member permissions available for basic-level access</p>
{% input_with_errors form.basic_org_domain_request_permissions %}
<p>Member permissions available for basic-level acccess.</p>

<h3 class="margin-bottom-0">Organization domain requests</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
{% input_with_errors form.basic_org_domain_request_permissions %}
{% endwith %}
</div>

<!-- Submit/cancel buttons -->
Expand All @@ -108,10 +118,76 @@ <h2>Basic member permissions</h2>
aria-label="Cancel adding new member"
>Cancel
</a>
<button type="submit" class="usa-button">Invite Member</button>
<a
id="invite_member_trigger"
href="#invite-member-modal"
class="usa-button usa-button--outline margin-top-1 display-none"
aria-controls="invite-member-modal"
data-open-modal
>Trigger invite member modal</a>
<button id="invite_new_member_submit" type="submit" class="usa-button">Invite Member</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<button id="invite_new_member_submit" type="submit" class="usa-button">Invite Member</button>
<button id="invite_new_member_submit" class="usa-button">Invite Member</button>

Assuming that this button just opens the model, this shouldn't be type="submit" right? I think we can trigger that with js

</div>
</form>

<div
class="usa-modal"
id="invite-member-modal"
aria-labelledby="invite-member-heading"
aria-describedby="confirm-invite-description"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Q) Is this id defined anywhere? I can't find it in the html (at least looking briefly)

style="display: none;"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
style="display: none;"

I think this is hidden by default via uswds. If this is appearing after page load, there may be a bug

>
<div class="usa-modal__content">
<div class="usa-modal__main">
<h2 class="usa-modal__heading" id="invite-member-heading">
Invite this member to the organization?
</h2>
<h3 class="summary-item__title
text-primary-dark">Member information and permissions</h3>
<div class="usa-prose">
<!-- Display email as a header and access level -->
<h4 class="text-primary">Email</h4>
<p class="margin-top-0" id="modalEmail"></p>

<h4 class="text-primary">Member Access</h4>
<p class="margin-top-0" id="modalAccessLevel"></p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick/optional) Maybe better described as modalMemberAccessLevel


<!-- Dynamic Permissions Details -->
<div id="permission_details"></div>
Comment on lines +154 to +155
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tricky. Nice job

</div>

<div class="usa-modal__footer">
<ul class="usa-button-group">
<li class="usa-button-group__item">
<button id="confirm_new_member_submit" type="submit" class="usa-button">Yes, invite member</button>
</li>
<li class="usa-button-group__item">
<button
type="button"
class="usa-button usa-button--unstyled"
data-close-modal
onclick="closeModal()"
>
Cancel
</button>
</li>
</ul>
</div>
</div>
<button
type="button"
class="usa-button usa-modal__close"
aria-label="Close this window"
data-close-modal
onclick="closeModal()"
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="{% static 'img/sprite.svg' %}#close"></use>
</svg>
</button>
</div>
</div>


{% endblock portfolio_content%}


Loading
Loading