diff --git a/src/Pipfile b/src/Pipfile
index fdf127d7c..07b1db715 100644
--- a/src/Pipfile
+++ b/src/Pipfile
@@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
-django = "4.2.10"
+django = "4.2.17"
cfenv = "*"
django-cors-headers = "*"
pycryptodomex = "*"
diff --git a/src/Pipfile.lock b/src/Pipfile.lock
index 33b858314..56daf5db5 100644
--- a/src/Pipfile.lock
+++ b/src/Pipfile.lock
@@ -1789,11 +1789,12 @@
},
"waitress": {
"hashes": [
- "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1",
- "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669"
+ "sha256:26cdbc593093a15119351690752c99adc13cbc6786d75f7b6341d1234a3730ac",
+ "sha256:ef0c1f020d9f12a515c4ec65c07920a702613afcad1dbfdc3bcec256b6c072b3"
],
- "markers": "python_full_version >= '3.8.0'",
- "version": "==3.0.0"
+ "index": "pypi",
+ "markers": "python_full_version >= '3.9.0'",
+ "version": "==3.0.1"
},
"webob": {
"hashes": [
diff --git a/src/package-lock.json b/src/package-lock.json
index 22fb31857..d78b5132f 100644
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -6921,16 +6921,6 @@
"validate-npm-package-license": "^3.0.1"
}
},
- "node_modules/normalize-package-data/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver"
- }
- },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -7307,39 +7297,6 @@
"node": ">= 12"
}
},
- "node_modules/pa11y/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "license": "ISC",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/pa11y/node_modules/semver": {
- "version": "7.3.8",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
- "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
- "license": "ISC",
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/pa11y/node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "license": "ISC"
- },
"node_modules/parse-filepath": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
@@ -8888,13 +8845,15 @@
}
},
"node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/semver-greatest-satisfied-range": {
diff --git a/src/package.json b/src/package.json
index e433d0126..7f7448bd2 100644
--- a/src/package.json
+++ b/src/package.json
@@ -22,5 +22,8 @@
"sass-loader": "^12.6.0",
"webpack": "^5.96.1",
"webpack-stream": "^7.0.0"
+ },
+ "overrides": {
+ "semver": "^7.5.3"
}
}
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 4465b7098..52e214bb9 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -1830,10 +1830,12 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
form = DomainRequestAdminForm
change_form_template = "django/admin/domain_request_change_form.html"
+ # ------ Filters ------
+ # Define custom filters
class StatusListFilter(MultipleChoiceListFilter):
"""Custom status filter which is a multiple choice filter"""
- title = "Status"
+ title = "status"
parameter_name = "status__in"
template = "django/admin/multiple_choice_list_filter.html"
@@ -1877,7 +1879,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
If we have a portfolio, use the portfolio's federal type. If not, use the
organization in the Domain Request object."""
- title = "federal Type"
+ title = "federal type"
parameter_name = "converted_federal_types"
def lookups(self, request, model_admin):
@@ -1965,13 +1967,58 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
if self.value() == "0":
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
+ class PortfolioFilter(admin.SimpleListFilter):
+ """Define a custom filter for portfolio"""
+
+ title = _("portfolio")
+ parameter_name = "portfolio__isnull"
+
+ def lookups(self, request, model_admin):
+ return (
+ ("1", _("Yes")),
+ ("0", _("No")),
+ )
+
+ def queryset(self, request, queryset):
+ if self.value() == "1":
+ return queryset.filter(Q(portfolio__isnull=False))
+ if self.value() == "0":
+ return queryset.filter(Q(portfolio__isnull=True))
+
+ # ------ Custom fields ------
+ def custom_election_board(self, obj):
+ return "Yes" if obj.is_election_board else "No"
+
+ custom_election_board.admin_order_field = "is_election_board" # type: ignore
+ custom_election_board.short_description = "Election office" # type: ignore
+
+ @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)
+
+ custom_requested_domain.admin_order_field = "requested_domain__name" # type: ignore
+
+ # ------ Converted fields ------
+ # These fields map to @Property methods and
+ # require these custom definitions to work properly
@admin.display(description=_("Generic Org Type"))
def converted_generic_org_type(self, obj):
return obj.converted_generic_org_type_display
@admin.display(description=_("Organization Name"))
def converted_organization_name(self, obj):
- return obj.converted_organization_name
+ # Example: Show different icons based on `status`
+ if obj.portfolio:
+ url = reverse("admin:registrar_portfolio_change", args=[obj.portfolio.id])
+ text = obj.converted_organization_name
+ return format_html('{}', url, text)
+ else:
+ return obj.converted_organization_name
@admin.display(description=_("Federal Agency"))
def converted_federal_agency(self, obj):
@@ -1989,34 +2036,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def converted_state_territory(self, obj):
return obj.converted_state_territory
- # Columns
- list_display = [
- "requested_domain",
- "first_submitted_date",
- "last_submitted_date",
- "last_status_update",
- "status",
- "custom_election_board",
- "converted_generic_org_type",
- "converted_organization_name",
- "converted_federal_agency",
- "converted_federal_type",
- "converted_city",
- "converted_state_territory",
- "investigator",
- ]
-
- orderable_fk_fields = [
- ("requested_domain", "name"),
- ("investigator", ["first_name", "last_name"]),
- ]
-
- def custom_election_board(self, obj):
- return "Yes" if obj.is_election_board else "No"
-
- custom_election_board.admin_order_field = "is_election_board" # type: ignore
- custom_election_board.short_description = "Election office" # type: ignore
-
+ # ------ Portfolio fields ------
# Define methods to display fields from the related portfolio
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
@@ -2086,10 +2106,33 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def status_history(self, obj):
return "No changelog to display."
- status_history.short_description = "Status History" # type: ignore
+ status_history.short_description = "Status history" # type: ignore
+
+ # Columns
+ list_display = [
+ "custom_requested_domain",
+ "first_submitted_date",
+ "last_submitted_date",
+ "last_status_update",
+ "status",
+ "custom_election_board",
+ "converted_generic_org_type",
+ "converted_organization_name",
+ "converted_federal_agency",
+ "converted_federal_type",
+ "converted_city",
+ "converted_state_territory",
+ "investigator",
+ ]
+
+ orderable_fk_fields = [
+ ("requested_domain", "name"),
+ ("investigator", ["first_name", "last_name"]),
+ ]
# Filters
list_filter = (
+ PortfolioFilter,
StatusListFilter,
GenericOrgFilter,
FederalTypeFilter,
@@ -2099,13 +2142,14 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
)
# Search
+ # NOTE: converted fields are included in the override for get_search_results
search_fields = [
"requested_domain__name",
"creator__email",
"creator__first_name",
"creator__last_name",
]
- search_help_text = "Search by domain or creator."
+ search_help_text = "Search by domain, creator, or organization name."
fieldsets = [
(
@@ -2271,9 +2315,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
"cisa_representative_first_name",
"cisa_representative_last_name",
"cisa_representative_email",
- "requested_suborganization",
- "suborganization_city",
- "suborganization_state_territory",
]
autocomplete_fields = [
@@ -2692,6 +2733,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
qs = qs.filter(portfolio=portfolio_id)
return qs
+ def get_search_results(self, request, queryset, search_term):
+ # Call the parent's method to apply default search logic
+ base_queryset, use_distinct = super().get_search_results(request, queryset, search_term)
+
+ # Add custom search logic for the annotated field
+ if search_term:
+ annotated_queryset = queryset.filter(
+ # converted_organization_name
+ Q(portfolio__organization_name__icontains=search_term)
+ | Q(portfolio__isnull=True, organization_name__icontains=search_term)
+ )
+
+ # Combine the two querysets using union
+ combined_queryset = base_queryset | annotated_queryset
+ else:
+ combined_queryset = base_queryset
+
+ return combined_queryset, use_distinct
+
class TransitionDomainAdmin(ListHeaderAdmin):
"""Custom transition domain admin class."""
@@ -3746,9 +3806,9 @@ class PortfolioAdmin(ListHeaderAdmin):
"senior_official",
]
- analyst_readonly_fields = [
- "organization_name",
- ]
+ # Even though this is empty, I will leave it as a stub for easy changes in the future
+ # rather than strip it out of our logic.
+ analyst_readonly_fields = [] # type: ignore
def get_admin_users(self, obj):
# Filter UserPortfolioPermission objects related to the portfolio
diff --git a/src/registrar/assets/src/js/getgov/domain-dnssec.js b/src/registrar/assets/src/js/getgov/domain-dnssec.js
new file mode 100644
index 000000000..860359fe0
--- /dev/null
+++ b/src/registrar/assets/src/js/getgov/domain-dnssec.js
@@ -0,0 +1,15 @@
+import { submitForm } from './helpers.js';
+
+export function initDomainDNSSEC() {
+ document.addEventListener('DOMContentLoaded', function() {
+ let domain_dnssec_page = document.getElementById("domain-dnssec");
+ if (domain_dnssec_page) {
+ const button = document.getElementById("disable-dnssec-button");
+ if (button) {
+ button.addEventListener("click", function () {
+ submitForm("disable-dnssec-form");
+ });
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/registrar/assets/src/js/getgov/domain-dsdata.js b/src/registrar/assets/src/js/getgov/domain-dsdata.js
new file mode 100644
index 000000000..7c0871bec
--- /dev/null
+++ b/src/registrar/assets/src/js/getgov/domain-dsdata.js
@@ -0,0 +1,27 @@
+import { submitForm } from './helpers.js';
+
+export function initDomainDSData() {
+ document.addEventListener('DOMContentLoaded', function() {
+ let domain_dsdata_page = document.getElementById("domain-dsdata");
+ if (domain_dsdata_page) {
+ const override_button = document.getElementById("disable-override-click-button");
+ const cancel_button = document.getElementById("btn-cancel-click-button");
+ const cancel_close_button = document.getElementById("btn-cancel-click-close-button");
+ if (override_button) {
+ override_button.addEventListener("click", function () {
+ submitForm("disable-override-click-form");
+ });
+ }
+ if (cancel_button) {
+ cancel_button.addEventListener("click", function () {
+ submitForm("btn-cancel-click-form");
+ });
+ }
+ if (cancel_close_button) {
+ cancel_close_button.addEventListener("click", function () {
+ submitForm("btn-cancel-click-form");
+ });
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/registrar/assets/src/js/getgov/domain-managers.js b/src/registrar/assets/src/js/getgov/domain-managers.js
new file mode 100644
index 000000000..26eccd8cd
--- /dev/null
+++ b/src/registrar/assets/src/js/getgov/domain-managers.js
@@ -0,0 +1,20 @@
+import { submitForm } from './helpers.js';
+
+export function initDomainManagersPage() {
+ document.addEventListener('DOMContentLoaded', function() {
+ let domain_managers_page = document.getElementById("domain-managers");
+ if (domain_managers_page) {
+ // Add event listeners for all buttons matching user-delete-button-{NUMBER}
+ const deleteButtons = document.querySelectorAll('[id^="user-delete-button-"]'); // Select buttons with ID starting with "user-delete-button-"
+ deleteButtons.forEach((button) => {
+ const buttonId = button.id; // e.g., "user-delete-button-1"
+ const number = buttonId.split('-').pop(); // Extract the NUMBER part
+ const formId = `user-delete-form-${number}`; // Generate the corresponding form ID
+
+ button.addEventListener("click", function () {
+ submitForm(formId); // Pass the form ID to submitForm
+ });
+ });
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/registrar/assets/src/js/getgov/domain-request-form.js b/src/registrar/assets/src/js/getgov/domain-request-form.js
new file mode 100644
index 000000000..d9b660a50
--- /dev/null
+++ b/src/registrar/assets/src/js/getgov/domain-request-form.js
@@ -0,0 +1,12 @@
+import { submitForm } from './helpers.js';
+
+export function initDomainRequestForm() {
+ document.addEventListener('DOMContentLoaded', function() {
+ const button = document.getElementById("domain-request-form-submit-button");
+ if (button) {
+ button.addEventListener("click", function () {
+ submitForm("submit-domain-request-form");
+ });
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/registrar/assets/src/js/getgov/form-errors.js b/src/registrar/assets/src/js/getgov/form-errors.js
new file mode 100644
index 000000000..ec1faaccf
--- /dev/null
+++ b/src/registrar/assets/src/js/getgov/form-errors.js
@@ -0,0 +1,19 @@
+export function initFormErrorHandling() {
+ document.addEventListener('DOMContentLoaded', function() {
+ const errorSummary = document.getElementById('form-errors');
+ const firstErrorField = document.querySelector('.usa-input--error');
+ if (firstErrorField) {
+ // Scroll to the first field in error
+ firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
+
+ // Add focus to the first field in error
+ setTimeout(() => {
+ firstErrorField.focus();
+ }, 50);
+ } else if (errorSummary) {
+ // Scroll to the error summary
+ errorSummary.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+
+ });
+}
\ No newline at end of file
diff --git a/src/registrar/assets/src/js/getgov/helpers.js b/src/registrar/assets/src/js/getgov/helpers.js
index 1afd84520..7d1449bac 100644
--- a/src/registrar/assets/src/js/getgov/helpers.js
+++ b/src/registrar/assets/src/js/getgov/helpers.js
@@ -1,9 +1,17 @@
export function hideElement(element) {
- element.classList.add('display-none');
+ if (element) {
+ element.classList.add('display-none');
+ } else {
+ throw new Error('hideElement expected a passed DOM element as an argument, but none was provided.');
+ }
};
export function showElement(element) {
- element.classList.remove('display-none');
+ if (element) {
+ element.classList.remove('display-none');
+ } else {
+ throw new Error('showElement expected a passed DOM element as an argument, but none was provided.');
+ }
};
/**
@@ -75,3 +83,16 @@ export function debounce(handler, cooldown=600) {
export function getCsrfToken() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value;
}
+
+/**
+ * Helper function to submit a form
+ * @param {} form_id - the id of the form to be submitted
+ */
+export function submitForm(form_id) {
+ let form = document.getElementById(form_id);
+ if (form) {
+ form.submit();
+ } else {
+ console.error("Form '" + form_id + "' not found.");
+ }
+}
diff --git a/src/registrar/assets/src/js/getgov/main.js b/src/registrar/assets/src/js/getgov/main.js
index f5ebc83a3..6ff402aa4 100644
--- a/src/registrar/assets/src/js/getgov/main.js
+++ b/src/registrar/assets/src/js/getgov/main.js
@@ -11,6 +11,11 @@ import { initMembersTable } from './table-members.js';
import { initMemberDomainsTable } from './table-member-domains.js';
import { initEditMemberDomainsTable } from './table-edit-member-domains.js';
import { initPortfolioNewMemberPageToggle, initAddNewMemberPageListeners, initPortfolioMemberPageRadio } from './portfolio-member-page.js';
+import { initDomainRequestForm } from './domain-request-form.js';
+import { initDomainManagersPage } from './domain-managers.js';
+import { initDomainDSData } from './domain-dsdata.js';
+import { initDomainDNSSEC } from './domain-dnssec.js';
+import { initFormErrorHandling } from './form-errors.js';
initDomainValidators();
@@ -36,6 +41,13 @@ initMembersTable();
initMemberDomainsTable();
initEditMemberDomainsTable();
+initDomainRequestForm();
+initDomainManagersPage();
+initDomainDSData();
+initDomainDNSSEC();
+
+initFormErrorHandling();
+
// Init the portfolio new member page
initPortfolioMemberPageRadio();
initPortfolioNewMemberPageToggle();
diff --git a/src/registrar/assets/src/js/getgov/table-base.js b/src/registrar/assets/src/js/getgov/table-base.js
index e526c6b5f..e1d5c11ce 100644
--- a/src/registrar/assets/src/js/getgov/table-base.js
+++ b/src/registrar/assets/src/js/getgov/table-base.js
@@ -143,7 +143,7 @@ export class BaseTable {
this.statusCheckboxes = document.querySelectorAll(`.${this.sectionSelector} input[name="filter-status"]`);
this.statusIndicator = document.getElementById(`${this.sectionSelector}__filter-indicator`);
this.statusToggle = document.getElementById(`${this.sectionSelector}__usa-button--filter`);
- this.noTableWrapper = document.getElementById(`${this.sectionSelector}__no-data`);
+ this.noDataTableWrapper = document.getElementById(`${this.sectionSelector}__no-data`);
this.noSearchResultsWrapper = document.getElementById(`${this.sectionSelector}__no-search-results`);
this.portfolioElement = document.getElementById('portfolio-js-value');
this.portfolioValue = this.portfolioElement ? this.portfolioElement.getAttribute('data-portfolio') : null;
@@ -451,7 +451,7 @@ export class BaseTable {
}
// handle the display of proper messaging in the event that no members exist in the list or search returns no results
- this.updateDisplay(data, this.tableWrapper, this.noTableWrapper, this.noSearchResultsWrapper, this.currentSearchTerm);
+ this.updateDisplay(data, this.tableWrapper, this.noDataTableWrapper, this.noSearchResultsWrapper, this.currentSearchTerm);
// identify the DOM element where the list of results will be inserted into the DOM
const tbody = this.tableWrapper.querySelector('tbody');
tbody.innerHTML = '';
@@ -495,7 +495,8 @@ export class BaseTable {
// Add event listeners to table headers for sorting
initializeTableHeaders() {
this.tableHeaders.forEach(header => {
- header.addEventListener('click', () => {
+ header.addEventListener('click', event => {
+ let button = header.querySelector('.usa-table__header__button')
const sortBy = header.getAttribute('data-sortable');
let order = 'asc';
// sort order will be ascending, unless the currently sorted column is ascending, and the user
@@ -505,6 +506,13 @@ export class BaseTable {
}
// load the results with the updated sort
this.loadTable(1, sortBy, order);
+ // If the click occurs outside of the button, need to simulate a button click in order
+ // for USWDS listener on the button to execute.
+ // Check first to see if click occurs outside of the button
+ if (!button.contains(event.target)) {
+ // Simulate a button click
+ button.click();
+ }
});
});
}
diff --git a/src/registrar/assets/src/js/getgov/table-domains.js b/src/registrar/assets/src/js/getgov/table-domains.js
index 20d9ef7de..a6373a5c2 100644
--- a/src/registrar/assets/src/js/getgov/table-domains.js
+++ b/src/registrar/assets/src/js/getgov/table-domains.js
@@ -31,6 +31,9 @@ export class DomainsTable extends BaseTable {
`
}
+ const isExpiring = domain.state_display === "Expiring soon"
+ const iconType = isExpiring ? "error_outline" : "info_outline";
+ const iconColor = isExpiring ? "text-secondary-vivid" : "text-accent-cool"
row.innerHTML = `
.gov is managed by the Cybersecurity and Infrastructure Security Agency. CISA has 10 regions that some organizations choose to work with. Regional representatives use titles like protective security advisors, cyber security advisors, or election security advisors.
+ {% endif %} {% else %} - {{ field.label }} + {% if span_for_text %} + {{ field.label }} + {% else %} + {{ field.label }} + {% endif %} {% endif %} {% if widget.attrs.required %} - - {% if field.label == "Is your organization an election office?" or field.label == "What .gov domain do you want?" or field.label == "I read and agree to the requirements for operating a .gov domain." or field.label == "Please explain why there are no other employees from your organization we can contact to help us assess your eligibility for a .gov domain." %} + + {% if field.widget_type == 'radioselect' %} + Select one. * + + {% elif field.label == "Is your organization an election office?" or field.label == "What .gov domain do you want?" or field.label == "I read and agree to the requirements for operating a .gov domain." or field.label == "Please explain why there are no other employees from your organization we can contact to help us assess your eligibility for a .gov domain." or field.label == "Has other contacts" %} {% else %} * {% endif %} diff --git a/src/registrar/templates/domain_add_user.html b/src/registrar/templates/domain_add_user.html index 1429127e6..b09f1f814 100644 --- a/src/registrar/templates/domain_add_user.html +++ b/src/registrar/templates/domain_add_user.html @@ -37,6 +37,9 @@ {% endif %} {% endblock breadcrumb %} + + {% include "includes/form_errors.html" with form=form %} +diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index add7ca725..a5b8e52cb 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -35,18 +35,27 @@ Status: + {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} Expired + {% elif has_domain_renewal_flag and domain.is_expiring %} + Expiring soon {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} DNS needed {% else %} - {{ domain.state|title }} + {{ domain.state|title }} {% endif %} {% if domain.get_state_help_text %}
DNSSEC, or DNS Security Extensions, is an additional security layer to protect your website. Enabling DNSSEC ensures that when someone visits your domain, they can be certain that it’s connecting to the correct server, preventing potential hijacking or tampering with your domain's records.
@@ -78,7 +78,11 @@ aria-labelledby="Are you sure you want to continue?" aria-describedby="Your DNSSEC records will be deleted from the registry." > - {% include 'includes/modal.html' with modal_heading="Are you sure you want to disable DNSSEC?" modal_button=modal_button|safe %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to disable DNSSEC?" modal_button_id="disable-dnssec-button" modal_button_text="Confirm" modal_button_class="usa-button--secondary" %} + {% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_dsdata.html b/src/registrar/templates/domain_dsdata.html index 0f60235e1..5ebb264c4 100644 --- a/src/registrar/templates/domain_dsdata.html +++ b/src/registrar/templates/domain_dsdata.html @@ -42,7 +42,7 @@ {% include "includes/form_errors.html" with form=form %} {% endfor %} -In order to enable DNSSEC, you must first configure it with your DNS hosting service.
@@ -141,7 +141,15 @@ aria-describedby="Your DNSSEC records will be deleted from the registry." data-force-action > - {% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to remove all DS records on your domain." modal_description="To fully disable DNSSEC: In addition to removing your DS records here, you’ll need to delete the DS records at your DNS host. To avoid causing your domain to appear offline, you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %} + {% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to remove all DS records on your domain." modal_description="To fully disable DNSSEC: In addition to removing your DS records here, you’ll need to delete the DS records at your DNS host. To avoid causing your domain to appear offline, you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button_id="disable-override-click-button" modal_button_text="Remove all DS data" modal_button_class="usa-button--secondary" %} + + {% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_request_additional_details.html b/src/registrar/templates/domain_request_additional_details.html index 2a581bbd2..86fa79fa3 100644 --- a/src/registrar/templates/domain_request_additional_details.html +++ b/src/registrar/templates/domain_request_additional_details.html @@ -9,15 +9,9 @@ {% block form_fields %} -