diff --git a/app/assets/javascripts/loginAlert.js b/app/assets/javascripts/loginAlert.js new file mode 100644 index 000000000..67ed439ae --- /dev/null +++ b/app/assets/javascripts/loginAlert.js @@ -0,0 +1,35 @@ +(function (window) { + +// Set the target date (10 days before March 15th, 2024) + const targetDate = new Date("April 9, 2024 00:00:00").getTime(); + + // Function to update the countdown display + function updateCountdown() { + const now = new Date().getTime(); + const difference = targetDate - now; + + // Time calculations for days only + const days = Math.floor(difference / (1000 * 60 * 60 * 24)); + + // Visibility logic + if (days < 0) { + // Hide if more than 10 days away OR if already past the date + document.getElementById("countdown-container").style.display = "none"; + } else { + // Show if 10 days or less remaining + document.getElementById("countdown-container").style.display = "block"; + document.getElementById("countdown").innerHTML = days + " days "; + } + + } + + // Expose the updateCountdown function to the outside world + window.updateCountdown = updateCountdown; + + // Initial display update + updateCountdown(); + + // Update the countdown every second (inside the IIFE) + setInterval(updateCountdown, 1000); + +})(window); diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index addf9cc7a..05cfab466 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -552,3 +552,9 @@ details form { .edit-textbox-error-mt { margin-top: 1.5rem; } + +// Login page + +#countdown-container { + display: none; // Hide the countdown timer +} diff --git a/app/main/forms.py b/app/main/forms.py index c01ce8fcc..2e6655cc5 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1781,12 +1781,16 @@ class TemplateAndFoldersSelectionForm(Form): None, [ # ('email', 'Email') if 'email' in available_template_types else None, - ("sms", "Start with a blank template") - if "sms" in available_template_types - else None, - ("copy-existing", "Copy an existing template") - if allow_adding_copy_of_template - else None, + ( + ("sms", "Start with a blank template") + if "sms" in available_template_types + else None + ), + ( + ("copy-existing", "Copy an existing template") + if allow_adding_copy_of_template + else None + ), ], ) ) diff --git a/app/main/views/api_keys.py b/app/main/views/api_keys.py index 4f8c3b29e..8cb28ba59 100644 --- a/app/main/views/api_keys.py +++ b/app/main/views/api_keys.py @@ -168,12 +168,14 @@ def api_callbacks(service_id): return render_template( "views/api/callbacks.html", - received_text_messages_callback=received_text_messages_callback["url"] - if received_text_messages_callback - else None, - delivery_status_callback=delivery_status_callback["url"] - if delivery_status_callback - else None, + received_text_messages_callback=( + received_text_messages_callback["url"] + if received_text_messages_callback + else None + ), + delivery_status_callback=( + delivery_status_callback["url"] if delivery_status_callback else None + ), ) @@ -262,9 +264,11 @@ def received_text_messages_callback(service_id): received_text_messages_callback = get_received_text_messages_callback() form = CallbackForm( - url=received_text_messages_callback.get("url") - if received_text_messages_callback - else "", + url=( + received_text_messages_callback.get("url") + if received_text_messages_callback + else "" + ), bearer_token=dummy_bearer_token if received_text_messages_callback else "", ) diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index 4ab20f363..1b80c659e 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -122,16 +122,18 @@ def edit_user_permissions(service_id, user_id): form = form_class.from_user( user, service_id, - folder_permissions=None - if user.platform_admin - else [ - f["id"] - for f in current_service.all_template_folders - if user.has_template_folder_permission(f) - ], - all_template_folders=None - if user.platform_admin - else current_service.all_template_folders, + folder_permissions=( + None + if user.platform_admin + else [ + f["id"] + for f in current_service.all_template_folders + if user.has_template_folder_permission(f) + ] + ), + all_template_folders=( + None if user.platform_admin else current_service.all_template_folders + ), ) if form.validate_on_submit(): diff --git a/app/main/views/performance.py b/app/main/views/performance.py index 13445967b..1d2a4d1bc 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -34,8 +34,8 @@ def performance(): stats["average_percentage_under_10_seconds"] = mean( [row["percentage_under_10_seconds"] for row in stats["processing_time"]] or [0] ) - stats[ - "count_of_live_services_and_organizations" - ] = status_api_client.get_count_of_live_services_and_organizations() + stats["count_of_live_services_and_organizations"] = ( + status_api_client.get_count_of_live_services_and_organizations() + ) return render_template("views/performance.html", **stats) diff --git a/app/main/views/send.py b/app/main/views/send.py index 4332c7ddb..6c786e3ac 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -52,12 +52,14 @@ def get_example_csv_fields(column_headers, use_example_as_example, submitted_fie def get_example_csv_rows(template, use_example_as_example=True, submitted_fields=False): return { - "email": ["test@example.com"] - if use_example_as_example - else [current_user.email_address], - "sms": ["12223334444"] - if use_example_as_example - else [current_user.mobile_number], + "email": ( + ["test@example.com"] + if use_example_as_example + else [current_user.email_address] + ), + "sms": ( + ["12223334444"] if use_example_as_example else [current_user.mobile_number] + ), }[template.template_type] + get_example_csv_fields( ( placeholder @@ -511,12 +513,14 @@ def _check_messages(service_id, template_id, upload_id, preview_row): template=template, max_initial_rows_shown=50, max_errors_shown=50, - guestlist=itertools.chain.from_iterable( - [user.name, user.mobile_number, user.email_address] - for user in Users(service_id) - ) - if current_service.trial_mode - else None, + guestlist=( + itertools.chain.from_iterable( + [user.name, user.mobile_number, user.email_address] + for user in Users(service_id) + ) + if current_service.trial_mode + else None + ), remaining_messages=remaining_messages, allow_international_sms=current_service.has_permission("international_sms"), ) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 1cac1410c..628ac59e5 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -477,9 +477,11 @@ def service_edit_email_reply_to(service_id, reply_to_email_id): current_service.id, reply_to_email_id=reply_to_email_id, email_address=form.email_address.data, - is_default=True - if reply_to_email_address["is_default"] - else form.is_default.data, + is_default=( + True + if reply_to_email_address["is_default"] + else form.is_default.data + ), ) return redirect(url_for(".service_email_reply_to", service_id=service_id)) try: @@ -499,9 +501,11 @@ def service_edit_email_reply_to(service_id, reply_to_email_id): ".service_verify_reply_to_address", service_id=service_id, notification_id=notification_id, - is_default=True - if reply_to_email_address["is_default"] - else form.is_default.data, + is_default=( + True + if reply_to_email_address["is_default"] + else form.is_default.data + ), replace=reply_to_email_id, ) ) @@ -702,9 +706,11 @@ def service_edit_sms_sender(service_id, sms_sender_id): service_api_client.update_sms_sender( current_service.id, sms_sender_id=sms_sender_id, - sms_sender=sms_sender["sms_sender"] - if is_inbound_number - else form.sms_sender.data.replace("\r", ""), + sms_sender=( + sms_sender["sms_sender"] + if is_inbound_number + else form.sms_sender.data.replace("\r", "") + ), is_default=True if sms_sender["is_default"] else form.is_default.data, ) return redirect(url_for(".service_sms_senders", service_id=service_id)) diff --git a/app/templates/views/signin.html b/app/templates/views/signin.html index 4ee655127..398a39274 100644 --- a/app/templates/views/signin.html +++ b/app/templates/views/signin.html @@ -13,8 +13,16 @@ {% block maincolumn_content %}
+
+
+

+ You have left to use login.gov to sign in +

+
+
+
+
- {% if again %}

You need to sign in again

{% if other_device %} diff --git a/gulpfile.js b/gulpfile.js index 6828bcaf1..79f976035 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -123,6 +123,7 @@ const javascripts = () => { paths.src + 'javascripts/homepage.js', paths.src + 'javascripts/timeoutPopup.js', paths.src + 'javascripts/date.js', + paths.src + 'javascripts/loginAlert.js', paths.src + 'javascripts/main.js', ]) .pipe(plugins.prettyerror()) diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index 1deeca299..900ff4bf5 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -218,7 +218,7 @@ def test_if_existing_user_accepts_twice_they_redirect_to_sign_in( assert ( page.h1.string, - page.select("main p")[0].text.strip(), + page.select("main p")[1].text.strip(), ) == ( "You need to sign in again", "We signed you out because you have not used Notify for a while.", @@ -332,7 +332,7 @@ def test_existing_user_of_service_get_redirected_to_signin( assert ( page.h1.string, - page.select("main p")[0].text.strip(), + page.select("main p")[1].text.strip(), ) == ( "You need to sign in again", "We signed you out because you have not used Notify for a while.", @@ -421,7 +421,7 @@ def test_existing_signed_out_user_accept_invite_redirects_to_sign_in( assert mock_accept_invite.call_count == 1 assert ( page.h1.string, - page.select("main p")[0].text.strip(), + page.select("main p")[1].text.strip(), ) == ( "You need to sign in again", "We signed you out because you have not used Notify for a while.", diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py index 9bdabf925..c3d128155 100644 --- a/tests/app/main/views/test_manage_users.py +++ b/tests/app/main/views/test_manage_users.py @@ -862,9 +862,9 @@ def test_should_show_page_if_prefilled_user_is_already_invited( mock_get_invites_for_service, platform_admin_user, ): - active_user_with_permission_to_other_service[ - "email_address" - ] = "user_1@testnotify.gsa.gov" + active_user_with_permission_to_other_service["email_address"] = ( + "user_1@testnotify.gsa.gov" + ) client_request.login(platform_admin_user) mocker.patch( "app.models.user.user_api_client.get_user", diff --git a/tests/app/main/views/test_template_folders.py b/tests/app/main/views/test_template_folders.py index fae020e58..0bf2fcb08 100644 --- a/tests/app/main/views/test_template_folders.py +++ b/tests/app/main/views/test_template_folders.py @@ -31,9 +31,11 @@ def _folder(name, folder_id=None, parent=None, users_with_permission=None): "name": name, "id": folder_id or str(uuid.uuid4()), "parent_id": parent, - "users_with_permission": users_with_permission - if users_with_permission is not None - else [sample_uuid()], + "users_with_permission": ( + users_with_permission + if users_with_permission is not None + else [sample_uuid()] + ), } diff --git a/tests/conftest.py b/tests/conftest.py index 6c4f94b4f..4c2ceeea9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -904,9 +904,11 @@ def create_service_templates(service_id, number_of_templates=4): "{}_template_{}".format(template_type, template_number), template_type, "{} template {} content".format(template_type, template_number), - subject="{} template {} subject".format(template_type, template_number) - if template_type == "email" - else None, + subject=( + "{} template {} subject".format(template_type, template_number) + if template_type == "email" + else None + ), ) ) @@ -1101,9 +1103,9 @@ def active_user_with_permission_to_other_service( active_user_with_permission_to_two_services["permissions"].pop(SERVICE_ONE_ID) active_user_with_permission_to_two_services["services"].pop(0) active_user_with_permission_to_two_services["name"] = "Service Two User" - active_user_with_permission_to_two_services[ - "email_address" - ] = "service-two-user@test.gsa.gov" + active_user_with_permission_to_two_services["email_address"] = ( + "service-two-user@test.gsa.gov" + ) return active_user_with_permission_to_two_services diff --git a/tests/javascripts/loginAlert.test.js b/tests/javascripts/loginAlert.test.js new file mode 100644 index 000000000..4bb0d3eaa --- /dev/null +++ b/tests/javascripts/loginAlert.test.js @@ -0,0 +1,48 @@ +beforeAll(() => { + jest.spyOn(global, 'setTimeout'); + const sessionTimerModule = require('../../app/assets/javascripts/loginAlert.js'); + window.GOVUK.modules.start(); +}); + +jest.useFakeTimers(); +const targetDate = new Date("March 5, 2024 00:00:00"); // Reference point + +test('Hides the countdown if more than 10 days away', () => { + jest.setSystemTime(targetDate.getTime() - 12 * 24 * 60 * 60 * 1000); // 12 days before + + window.updateCountdown(); // Update the countdown display + + expect(document.getElementById("countdown-container").style.display).toBe("none"); +}); + +test('Shows the countdown if 10 days or less away', () => { + jest.setSystemTime(targetDate.getTime() - 8 * 24 * 60 * 60 * 1000); + + window.updateCountdown(); + + expect(document.getElementById("countdown-container").style.display).toBe("block"); +}); + +test('Displays the correct number of days', () => { + jest.setSystemTime(targetDate.getTime() - 5 * 24 * 60 * 60 * 1000); + + window.updateCountdown(); + + expect(document.getElementById("countdown").textContent).toBe("5 days "); +}); + +test('Hides the countdown if the target date has passed', () => { + jest.setSystemTime(targetDate.getTime() + 2 * 24 * 60 * 60 * 1000); + + window.updateCountdown(); + + expect(document.getElementById("countdown-container").style.display).toBe("none"); +}); + +test('Displays "Countdown Complete!" when the countdown finishes', () => { + jest.setSystemTime(targetDate.getTime()); + + window.updateCountdown(); + + expect(document.getElementById("countdown").textContent).toBe("Countdown Complete!"); +});