diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 2d2b90a5f..4b05bbb6d 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1327,6 +1327,7 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin): search_help_text = "Search by first name, last name, email, or portfolio." change_form_template = "django/admin/user_portfolio_permission_change_form.html" + delete_confirmation_template = "django/admin/user_portfolio_permission_delete_confirmation.html" def get_roles(self, obj): readable_roles = obj.get_readable_roles() @@ -1670,6 +1671,7 @@ class PortfolioInvitationAdmin(BaseInvitationAdmin): autocomplete_fields = ["portfolio"] change_form_template = "django/admin/portfolio_invitation_change_form.html" + delete_confirmation_template = "django/admin/portfolio_invitation_delete_confirmation.html" # Select portfolio invitations to change -> Portfolio invitations def changelist_view(self, request, extra_context=None): @@ -2287,11 +2289,12 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): @admin.display(description=_("Requested Domain")) def custom_requested_domain(self, obj): # Example: Show different icons based on `status` - url = reverse("admin:registrar_domainrequest_changelist") + f"{obj.id}" text = obj.requested_domain if obj.portfolio: - return format_html(' {}', url, text) - return format_html('{}', url, text) + return format_html( + f'{escape(text)}' + ) + return text custom_requested_domain.admin_order_field = "requested_domain__name" # type: ignore @@ -3738,11 +3741,13 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Using variables to get past the linter message1 = f"Cannot delete Domain when in state {obj.state}" message2 = f"This subdomain is being used as a hostname on another domain: {err.note}" + message3 = f"Command failed with note: {err.note}" # Human-readable mappings of ErrorCodes. Can be expanded. error_messages = { # noqa on these items as black wants to reformat to an invalid length ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION: message1, ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION: message2, + ErrorCode.COMMAND_FAILED: message3, } message = "Cannot connect to the registry" diff --git a/src/registrar/assets/src/js/getgov-admin/button-utils.js b/src/registrar/assets/src/js/getgov-admin/button-utils.js new file mode 100644 index 000000000..e3746d289 --- /dev/null +++ b/src/registrar/assets/src/js/getgov-admin/button-utils.js @@ -0,0 +1,15 @@ +/** + * Initializes buttons to behave like links by navigating to their data-url attribute + * Example usage: + */ +export function initButtonLinks() { + document.querySelectorAll('button.use-button-as-link').forEach(button => { + button.addEventListener('click', function() { + // Equivalent to button.getAttribute("data-href") + const href = this.dataset.href; + if (href) { + window.location.href = href; + } + }); + }); +} diff --git a/src/registrar/assets/src/js/getgov-admin/main.js b/src/registrar/assets/src/js/getgov-admin/main.js index 5c6de20ab..7eb1fc8cd 100644 --- a/src/registrar/assets/src/js/getgov-admin/main.js +++ b/src/registrar/assets/src/js/getgov-admin/main.js @@ -16,6 +16,7 @@ import { initDynamicPortfolioFields } from './portfolio-form.js'; import { initDynamicDomainInformationFields } from './domain-information-form.js'; import { initDynamicDomainFields } from './domain-form.js'; import { initAnalyticsDashboard } from './analytics.js'; +import { initButtonLinks } from './button-utils.js'; // General initModals(); @@ -23,6 +24,7 @@ initCopyToClipboard(); initFilterHorizontalWidget(); initDescriptions(); initSubmitBar(); +initButtonLinks(); // Domain request initIneligibleModal(); diff --git a/src/registrar/assets/src/js/getgov/helpers.js b/src/registrar/assets/src/js/getgov/helpers.js index 7d1449bac..08be011c2 100644 --- a/src/registrar/assets/src/js/getgov/helpers.js +++ b/src/registrar/assets/src/js/getgov/helpers.js @@ -96,3 +96,14 @@ export function submitForm(form_id) { console.error("Form '" + form_id + "' not found."); } } + +/** + * Helper function to strip HTML tags + * THIS IS NOT SUITABLE FOR SANITIZING DANGEROUS STRINGS + */ +export function unsafeStripHtmlTags(input) { + const tempDiv = document.createElement("div"); + // NOTE: THIS IS NOT SUITABLE FOR SANITIZING DANGEROUS STRINGS + tempDiv.innerHTML = input; + return tempDiv.textContent || tempDiv.innerText || ""; +} 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 95723fc7e..96961e5dc 100644 --- a/src/registrar/assets/src/js/getgov/portfolio-member-page.js +++ b/src/registrar/assets/src/js/getgov/portfolio-member-page.js @@ -18,7 +18,7 @@ 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, `More Options for ${member_name}`); + wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `More Options for ${member_name}`, "usa-icon--large"); // 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 @@ -100,8 +100,8 @@ export function initAddNewMemberPageListeners() { const permissionSections = document.querySelectorAll(`#${permission_details_div_id} > h3`); permissionSections.forEach(section => { - // Find the

element text - const sectionTitle = section.textContent; + // Find the

element text, strip out the '*' + const sectionTitle = section.textContent.trim().replace(/\*$/, "") + ": "; // Find the associated radio buttons container (next fieldset) const fieldset = section.nextElementSibling; @@ -128,25 +128,29 @@ export function initAddNewMemberPageListeners() { }); } else { // for admin users, the permissions are always the same - appendPermissionInContainer('Domains', 'Viewer', permissionDetailsContainer); - appendPermissionInContainer('Domain requests', 'Creator', permissionDetailsContainer); - appendPermissionInContainer('Members', 'Manager', permissionDetailsContainer); + appendPermissionInContainer('Domains: ', 'Viewer', permissionDetailsContainer); + appendPermissionInContainer('Domain requests: ', 'Creator', permissionDetailsContainer); + appendPermissionInContainer('Members: ', 'Manager', permissionDetailsContainer); } } function appendPermissionInContainer(sectionTitle, permissionDisplay, permissionContainer) { // Create new elements for the content - const titleElement = document.createElement("h4"); - titleElement.textContent = sectionTitle; - titleElement.classList.add("text-primary", "margin-bottom-0"); + const elementContainer = document.createElement("p"); + elementContainer.classList.add("margin-top-0", "margin-bottom-1"); - const permissionElement = document.createElement("p"); + const titleElement = document.createElement("strong"); + titleElement.textContent = sectionTitle; + titleElement.classList.add("text-primary-darker"); + + const permissionElement = document.createElement("span"); permissionElement.textContent = permissionDisplay; - permissionElement.classList.add("margin-top-0"); // Append to the content container - permissionContainer.appendChild(titleElement); - permissionContainer.appendChild(permissionElement); + elementContainer.appendChild(titleElement); + elementContainer.appendChild(permissionElement); + + permissionContainer.appendChild(elementContainer); } /* diff --git a/src/registrar/assets/src/js/getgov/table-base.js b/src/registrar/assets/src/js/getgov/table-base.js index ce4397887..bf561fa1f 100644 --- a/src/registrar/assets/src/js/getgov/table-base.js +++ b/src/registrar/assets/src/js/getgov/table-base.js @@ -79,13 +79,13 @@ export function addModal(id, ariaLabelledby, ariaDescribedby, modalHeading, moda * @param {string} modal_button_text - The action button's text * @param {string} screen_reader_text - A screen reader helper */ -export function generateKebabHTML(action, unique_id, modal_button_text, screen_reader_text) { +export function generateKebabHTML(action, unique_id, modal_button_text, screen_reader_text, icon_class) { const generateModalButton = (mobileOnly = false) => ` @@ -99,7 +99,7 @@ export function generateKebabHTML(action, unique_id, modal_button_text, screen_r // Main kebab structure const kebab = ` ${generateModalButton(true)} -
+
-