diff --git a/src/registrar/assets/src/js/getgov/portfolio-member-page.js b/src/registrar/assets/src/js/getgov/portfolio-member-page.js index cfb83badc..d3422b722 100644 --- a/src/registrar/assets/src/js/getgov/portfolio-member-page.js +++ b/src/registrar/assets/src/js/getgov/portfolio-member-page.js @@ -18,11 +18,11 @@ export function initPortfolioNewMemberPageToggle() { const unique_id = `${member_type}-${member_id}`; let cancelInvitationButton = member_type === "invitedmember" ? "Cancel invitation" : "Remove member"; - wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `for ${member_name}`); + wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `More Options for ${member_name}`); // This easter egg is only for fixtures that dont have names as we are displaying their emails // All prod users will have emails linked to their account - MembersTable.addMemberModal(num_domains, member_email || "Samwise Gamgee", member_delete_url, unique_id, wrapperDeleteAction); + MembersTable.addMemberDeleteModal(num_domains, member_email || member_name || "Samwise Gamgee", member_delete_url, unique_id, wrapperDeleteAction); uswdsInitializeModals(); diff --git a/src/registrar/assets/src/js/getgov/table-base.js b/src/registrar/assets/src/js/getgov/table-base.js index 338d5d98c..0abfee9b6 100644 --- a/src/registrar/assets/src/js/getgov/table-base.js +++ b/src/registrar/assets/src/js/getgov/table-base.js @@ -93,7 +93,6 @@ export function generateKebabHTML(action, unique_id, modal_button_text, screen_r ` : ''} ${modal_button_text} - ${screen_reader_text} `; @@ -107,6 +106,7 @@ export function generateKebabHTML(action, unique_id, modal_button_text, screen_r class="usa-button usa-button--unstyled usa-button--with-icon usa-accordion__button usa-button--more-actions" aria-expanded="false" aria-controls="more-actions-${unique_id}" + aria-label="${screen_reader_text}" >

Loading table.

'; let url = `${baseUrlValue}?${searchParams.toString()}` fetch(url) .then(response => response.json()) @@ -469,7 +473,6 @@ export class BaseTable { let dataObjects = this.getDataObjects(data); let customTableOptions = this.customizeTable(data); - dataObjects.forEach(dataObject => { this.addRow(dataObject, tbody, customTableOptions); }); diff --git a/src/registrar/assets/src/js/getgov/table-edit-member-domains.js b/src/registrar/assets/src/js/getgov/table-edit-member-domains.js index 4f0b1d610..19e36c902 100644 --- a/src/registrar/assets/src/js/getgov/table-edit-member-domains.js +++ b/src/registrar/assets/src/js/getgov/table-edit-member-domains.js @@ -103,8 +103,9 @@ export class EditMemberDomainsTable extends BaseTable { disabled = true; } + // uses margin-right-neg-5 as a hack to increase the text-wrapping width on this table row.innerHTML = ` - +
@@ -119,10 +121,10 @@ export class EditMemberDomainsTable extends BaseTable { ${domain.id}
- + ${domain.name} - ${disabled ? 'Domains must have one domain manager. To unassign this member, the domain needs another domain manager.' : ''} + ${disabled ? 'Domains must have one domain manager. To unassign this member, the domain needs another domain manager.' : ''} `; tbody.appendChild(row); @@ -235,7 +237,8 @@ export class EditMemberDomainsTable extends BaseTable { // Create unassigned domains list const unassignedDomainsList = document.createElement('ul'); unassignedDomainsList.classList.add('usa-list', 'usa-list--unstyled'); - this.removedDomains.forEach(removedDomain => { + let removedDomainsCopy = [...this.removedDomains].sort((a, b) => a.name.localeCompare(b.name)); + removedDomainsCopy.forEach(removedDomain => { const removedDomainListItem = document.createElement('li'); removedDomainListItem.textContent = removedDomain.name; // Use textContent for security unassignedDomainsList.appendChild(removedDomainListItem); @@ -244,7 +247,8 @@ export class EditMemberDomainsTable extends BaseTable { // Create assigned domains list const assignedDomainsList = document.createElement('ul'); assignedDomainsList.classList.add('usa-list', 'usa-list--unstyled'); - this.addedDomains.forEach(addedDomain => { + let addedDomainsCopy = [...this.addedDomains].sort((a, b) => a.name.localeCompare(b.name)); + addedDomainsCopy.forEach(addedDomain => { const addedDomainListItem = document.createElement('li'); addedDomainListItem.textContent = addedDomain.name; // Use textContent for security assignedDomainsList.appendChild(addedDomainListItem); @@ -259,7 +263,7 @@ export class EditMemberDomainsTable extends BaseTable { // Append unassigned domains section if (this.removedDomains.length) { const unassignedHeader = document.createElement('h3'); - unassignedHeader.classList.add('margin-bottom-1'); + unassignedHeader.classList.add('margin-bottom-05', 'h4'); unassignedHeader.textContent = 'Unassigned domains'; domainAssignmentSummary.appendChild(unassignedHeader); domainAssignmentSummary.appendChild(unassignedDomainsList); @@ -268,7 +272,8 @@ export class EditMemberDomainsTable extends BaseTable { // Append assigned domains section if (this.addedDomains.length) { const assignedHeader = document.createElement('h3'); - assignedHeader.classList.add('margin-bottom-1'); + // Make this h3 look like a h4 + assignedHeader.classList.add('margin-bottom-05', 'h4'); assignedHeader.textContent = 'Assigned domains'; domainAssignmentSummary.appendChild(assignedHeader); domainAssignmentSummary.appendChild(assignedDomainsList); @@ -276,7 +281,8 @@ export class EditMemberDomainsTable extends BaseTable { // Append total assigned domains section const totalHeader = document.createElement('h3'); - totalHeader.classList.add('margin-bottom-1'); + // Make this h3 look like a h4 + totalHeader.classList.add('margin-bottom-05', 'h4'); totalHeader.textContent = 'Total assigned domains'; domainAssignmentSummary.appendChild(totalHeader); const totalCount = document.createElement('p'); @@ -289,6 +295,7 @@ export class EditMemberDomainsTable extends BaseTable { this.updateReadonlyDisplay(); hideElement(this.editModeContainer); showElement(this.readonlyModeContainer); + window.scrollTo(0, 0); } showEditMode() { diff --git a/src/registrar/assets/src/js/getgov/table-member-domains.js b/src/registrar/assets/src/js/getgov/table-member-domains.js index 7f89eee52..f9b789e1f 100644 --- a/src/registrar/assets/src/js/getgov/table-member-domains.js +++ b/src/registrar/assets/src/js/getgov/table-member-domains.js @@ -19,9 +19,9 @@ export class MemberDomainsTable extends BaseTable { const domain = dataObject; const row = document.createElement('tr'); row.innerHTML = ` - + ${domain.name} - + `; tbody.appendChild(row); } diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index 2e3130a3e..75a7c29ac 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -78,13 +78,12 @@ export class MembersTable extends BaseTable { const num_domains = member.domain_urls.length; const last_active = this.handleLastActive(member.last_active); let cancelInvitationButton = member.type === "invitedmember" ? "Cancel invitation" : "Remove member"; - const kebabHTML = customTableOptions.hasAdditionalActions ? generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `for ${member.name}`): ''; + const kebabHTML = customTableOptions.hasAdditionalActions ? generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `Expand for more options for ${member.name}`): ''; const row = document.createElement('tr'); - let admin_tagHTML = ``; if (member.is_admin) - admin_tagHTML = `Admin` + admin_tagHTML = `Admin` // generate html blocks for domains and permissions for the member let domainsHTML = this.generateDomainsHTML(num_domains, member.domain_names, member.domain_urls, member.action_url); @@ -99,7 +98,8 @@ export class MembersTable extends BaseTable { type="button" class="usa-button--show-more-button usa-button usa-button--unstyled display-block margin-top-1" data-for=${unique_id} - aria-label="Expand for additional information" + aria-label="Expand for additional information for ${member.member_display}" + aria-label-placeholder="${member.member_display}" > Expand
"; - domainsHTML += "

Domains assigned

"; - domainsHTML += `

This member is assigned to ${num_domains} domains:

`; + domainsHTML += "

Domains assigned

"; + domainsHTML += `

This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:

`; domainsHTML += ""; // If there are more than 6 domains, display a "View assigned domains" link - if (num_domains >= 6) { - domainsHTML += `

View assigned domains

`; - } + domainsHTML += `

View assigned domains

`; domainsHTML += "
"; } @@ -378,34 +390,37 @@ export class MembersTable extends BaseTable { generatePermissionsHTML(member_permissions, UserPortfolioPermissionChoices) { let permissionsHTML = ''; + // Define shared classes across elements for easier refactoring + let sharedParagraphClasses = "font-body-xs text-base-dark margin-top-1 p--blockquote"; + // Check domain-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)) { - permissionsHTML += "

Domains: Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).

"; + permissionsHTML += `

Domains: Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).

`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)) { - permissionsHTML += "

Domains: Can manage domains they are assigned to and edit information about the domain (including DNS settings).

"; + permissionsHTML += `

Domains: Can manage domains they are assigned to and edit information about the domain (including DNS settings).

`; } // Check request-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_REQUESTS)) { - permissionsHTML += "

Domain requests: Can view all organization domain requests. Can create domain requests and modify their own requests.

"; + permissionsHTML += `

Domain requests: Can view all organization domain requests. Can create domain requests and modify their own requests.

`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS)) { - permissionsHTML += "

Domain requests (view-only): Can view all organization domain requests. Can't create or modify any domain requests.

"; + permissionsHTML += `

Domain requests (view-only): Can view all organization domain requests. Can't create or modify any domain requests.

`; } // Check member-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_MEMBERS)) { - permissionsHTML += "

Members: Can manage members including inviting new members, removing current members, and assigning domains to members.

"; + permissionsHTML += `

Members: Can manage members including inviting new members, removing current members, and assigning domains to members.

`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MEMBERS)) { - permissionsHTML += "

Members (view-only): Can view all organizational members. Can't manage any members.

"; + permissionsHTML += `

Members (view-only): Can view all organizational members. Can't manage any members.

`; } // If no specific permissions are assigned, display a message indicating no additional permissions if (!permissionsHTML) { - permissionsHTML += "

No additional permissions: There are no additional permissions for this member.

"; + permissionsHTML += `

No additional permissions: There are no additional permissions for this member.

`; } // Add a permissions header and wrap the entire output in a container - permissionsHTML = "

Additional permissions for this member

" + permissionsHTML + "
"; + permissionsHTML = `

Additional permissions for this member

${permissionsHTML}
`; return permissionsHTML; } @@ -423,7 +438,7 @@ export class MembersTable extends BaseTable { let modalDescription = ``; if (num_domains >= 0){ - modalHeading = `Are you sure you want to delete ${member_email}?`; + modalHeading = `Are you sure you want to remove ${member_email} from the organization?`; modalDescription = `They will no longer be able to access this organization. This action cannot be undone.`; if (num_domains >= 1) diff --git a/src/registrar/assets/src/sass/_theme/_buttons.scss b/src/registrar/assets/src/sass/_theme/_buttons.scss index 42628b653..13bc163a8 100644 --- a/src/registrar/assets/src/sass/_theme/_buttons.scss +++ b/src/registrar/assets/src/sass/_theme/_buttons.scss @@ -246,6 +246,10 @@ a.text-secondary:hover { color: $theme-color-error; } +.usa-button.usa-button--secondary { + background-color: $theme-color-error; +} + .usa-button--show-more-button { font-size: size('ui', 'xs'); text-decoration: none; diff --git a/src/registrar/assets/src/sass/_theme/_modals.scss b/src/registrar/assets/src/sass/_theme/_modals.scss new file mode 100644 index 000000000..44107790d --- /dev/null +++ b/src/registrar/assets/src/sass/_theme/_modals.scss @@ -0,0 +1,5 @@ +@use "uswds-core" as *; + +.usa-modal__main { + padding: 0 2rem 2rem; +} diff --git a/src/registrar/assets/src/sass/_theme/_tables.scss b/src/registrar/assets/src/sass/_theme/_tables.scss index ea160396e..a8a829a45 100644 --- a/src/registrar/assets/src/sass/_theme/_tables.scss +++ b/src/registrar/assets/src/sass/_theme/_tables.scss @@ -41,6 +41,13 @@ th { } } +// The member table has an extra "expand" row, which looks like a single row. +// But the DOM disagrees - so we basically need to hide the border on both rows. +#members__table-wrapper .dotgov-table tr:nth-last-child(2) td, +#members__table-wrapper .dotgov-table tr:nth-last-child(2) th { + border-bottom: none; +} + .dotgov-table { width: 100%; @@ -56,10 +63,9 @@ th { border: none; } - tr:not(.hide-td-borders) { - td, th { - border-bottom: 1px solid color('base-lighter'); - } + tr:not(.hide-td-borders):not(:last-child) td, + tr:not(.hide-td-borders):not(:last-child) th { + border-bottom: 1px solid color('base-lighter'); } thead th { diff --git a/src/registrar/assets/src/sass/_theme/_tags.scss b/src/registrar/assets/src/sass/_theme/_tags.scss new file mode 100644 index 000000000..495bb93a8 --- /dev/null +++ b/src/registrar/assets/src/sass/_theme/_tags.scss @@ -0,0 +1,3 @@ +.usa-tag { + text-transform: none; +} diff --git a/src/registrar/assets/src/sass/_theme/_uswds-theme.scss b/src/registrar/assets/src/sass/_theme/_uswds-theme.scss index 1661a6388..21bb48e96 100644 --- a/src/registrar/assets/src/sass/_theme/_uswds-theme.scss +++ b/src/registrar/assets/src/sass/_theme/_uswds-theme.scss @@ -68,6 +68,7 @@ in the form $setting: value, /*--------------------------- ## Font weights ----------------------------*/ + $theme-font-weight-medium: 400, $theme-font-weight-semibold: 600, /*--------------------------- diff --git a/src/registrar/assets/src/sass/_theme/styles.scss b/src/registrar/assets/src/sass/_theme/styles.scss index 493ebd542..4962bf184 100644 --- a/src/registrar/assets/src/sass/_theme/styles.scss +++ b/src/registrar/assets/src/sass/_theme/styles.scss @@ -26,6 +26,8 @@ @forward "header"; @forward "register-form"; @forward "containers"; +@forward "modals"; +@forward "tags"; /*-------------------------------------------------- --- Admin ---------------------------------*/ diff --git a/src/registrar/fixtures/fixtures_users.py b/src/registrar/fixtures/fixtures_users.py index e40998231..f1623e674 100644 --- a/src/registrar/fixtures/fixtures_users.py +++ b/src/registrar/fixtures/fixtures_users.py @@ -196,6 +196,7 @@ class UserFixture: "username": "b6a15987-5c88-4e26-8de2-ca71a0bdb2cd", "first_name": "Alysia-Analyst", "last_name": "Alysia-Analyst", + "email": "abroddrick+1@truss.works", }, { "username": "91a9b97c-bd0a-458d-9823-babfde7ebf44", @@ -361,22 +362,30 @@ def _get_existing_users(users): @staticmethod def _prepare_new_users(users, existing_usernames, existing_user_ids, are_superusers): - return [ - User( - id=user_data.get("id"), - first_name=user_data.get("first_name"), - last_name=user_data.get("last_name"), - username=user_data.get("username"), - email=user_data.get("email", ""), - title=user_data.get("title", "Peon"), - phone=user_data.get("phone", "2022222222"), - is_active=user_data.get("is_active", True), - is_staff=True, - is_superuser=are_superusers, - ) - for user_data in users - if user_data.get("username") not in existing_usernames and user_data.get("id") not in existing_user_ids - ] + new_users = [] + for i, user_data in enumerate(users): + username = user_data.get("username") + id = user_data.get("id") + first_name = user_data.get("first_name", "Bob") + last_name = user_data.get("last_name", "Builder") + + default_email = f"placeholder.{first_name.lower()}.{last_name.lower()}+{i}@igorville.gov" + email = user_data.get("email", default_email) + if username not in existing_usernames and id not in existing_user_ids: + user = User( + id=id, + first_name=first_name, + last_name=last_name, + username=username, + email=email, + title=user_data.get("title", "Peon"), + phone=user_data.get("phone", "2022222222"), + is_active=user_data.get("is_active", True), + is_staff=True, + is_superuser=are_superusers, + ) + new_users.append(user) + return new_users @staticmethod def _create_new_users(new_users): diff --git a/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html b/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html index fe62f268b..d07e5abf4 100644 --- a/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html +++ b/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html @@ -30,7 +30,7 @@ {{ member.user.phone }} {% for role in member.user|portfolio_role_summary:original %} - {{ role }} + {{ role }} {% endfor %} diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 48e39df42..8f26a7f98 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -65,7 +65,7 @@

Domain managers

{{ item.permission.user.email }} - {% if item.has_admin_flag %}Admin{% endif %} + {% if item.has_admin_flag %}Admin{% endif %} {% if not portfolio %}{{ item.permission.role|title }}{% endif %} @@ -160,7 +160,7 @@

Invitations

{{ invitation.domain_invitation.email }} - {% if invitation.has_admin_flag %}Admin{% endif %} + {% if invitation.has_admin_flag %}Admin{% endif %} {{ invitation.domain_invitation.created_at|date }} {% if not portfolio %}{{ invitation.domain_invitation.status|title }}{% endif %} diff --git a/src/registrar/templates/includes/form_messages.html b/src/registrar/templates/includes/form_messages.html index 59ecb4eaa..9d94387b3 100644 --- a/src/registrar/templates/includes/form_messages.html +++ b/src/registrar/templates/includes/form_messages.html @@ -1,7 +1,7 @@ {% if messages %} {% for message in messages %}
-
+
{{ message }}
diff --git a/src/registrar/templates/includes/member_domains_edit_table.html b/src/registrar/templates/includes/member_domains_edit_table.html index dec0b2623..0b8ff005a 100644 --- a/src/registrar/templates/includes/member_domains_edit_table.html +++ b/src/registrar/templates/includes/member_domains_edit_table.html @@ -23,7 +23,7 @@ {% comment %} Stores the json endpoint in a url for easier access {% endcomment %} {% url 'get_member_domains_json' as url %} -
+

Edit domains assigned to @@ -37,7 +37,7 @@