diff --git a/api.md b/api.md index 029ddaf24..0c51357cd 100644 --- a/api.md +++ b/api.md @@ -121,7 +121,6 @@ This page documents the Fronstage ui endpoints that can be hit. `/account` * GET and POST * GET and POST `/change-password` -* POST `/change-account-email-address` * GET and POST `/change-account-details` * GET `/something-else` Gets the something else once the option is selected * POST `/something-else` Sends secure message for the something else pages diff --git a/frontstage/models.py b/frontstage/models.py index 1c06de1ff..47a684555 100644 --- a/frontstage/models.py +++ b/frontstage/models.py @@ -288,30 +288,38 @@ class ContactDetailsChangeForm(FlaskForm): first_name = StringField( _("First name"), validators=[ - DataRequired(_("First name is required")), + DataRequired(_("Enter your first name")), Length(max=254, message=_("Your first name must be less than 254 " "characters")), ], ) last_name = StringField( _("Last name"), validators=[ - DataRequired(_("Last name is required")), + DataRequired(_("Enter your last name")), Length(max=254, message=_("Your last name must be less than 254 characters")), ], ) phone_number = StringField( _("Telephone number"), validators=[ - DataRequired(_("Phone number is required")), - Length(min=9, max=15, message=_("This should be a valid phone number between 9 and 15 " "digits")), + DataRequired(_("Enter a phone number")), + Length(min=9, max=15, message=_("Enter a 9 to 15 digit number")), ], default=None, ) email_address = StringField( _("Email address"), validators=[ - DataRequired(_("Email address is required")), - Email(message=_("Invalid email address")), + DataRequired(_("Enter an email address")), + Email(message=_("Enter an email address in the correct format, for example, name@example.com")), + Length(max=254, message=_("Your email must have fewer than 254 characters")), + ], + ) + confirm_email_address = StringField( + _("Confirm email address"), + validators=[ + DataRequired(_("Enter email addresses that match")), + EqualTo("email_address", "Enter email addresses that match"), Length(max=254, message=_("Your email must have fewer than 254 characters")), ], ) diff --git a/frontstage/templates/account/account-change-email-address-almost-done.html b/frontstage/templates/account/account-change-email-address-almost-done.html deleted file mode 100644 index 753ea444c..000000000 --- a/frontstage/templates/account/account-change-email-address-almost-done.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "layouts/_block_content.html" %} -{% from "components/breadcrumbs/_macro.njk" import onsBreadcrumbs %} -{% from "components/fieldset/_macro.njk" import onsFieldset %} -{% from "components/button/_macro.njk" import onsButton %} - -{% set page_title = "Change email address" %} -{% set breadcrumbsData = [ - { - "text": "Surveys", - "url": "/surveys/todo", - "id": "b-item-1" - }, - { - "text": "Account", - "url": "/my-account", - "id": "b-item-2" - }, - { - "text": "Change your contact details", - "url": "/my-account/change-account-details", - "id": "b-item-3" - }, -] %} - -{% block breadcrumbs %} - {{ - onsBreadcrumbs({ - "ariaLabel": "Breadcrumbs", - "id": "breadcrumbs", - "itemsList": breadcrumbsData - }) - }} -{% endblock breadcrumbs %} - -{% block main %} -

Almost done

-

We have sent a verification email to your new email address.

-

Follow the link in the email to verify the change.

-

Email not arrived? It may be in your junk folder.

-

If it does not arrive within 15 minutes, please contact us.

-
- Back to surveys -
-{% endblock main %} diff --git a/frontstage/templates/account/account-change-email-address-conflict.html b/frontstage/templates/account/account-change-email-address-conflict.html deleted file mode 100644 index fec108f2e..000000000 --- a/frontstage/templates/account/account-change-email-address-conflict.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "layouts/_block_content.html" %} -{% from "components/breadcrumbs/_macro.njk" import onsBreadcrumbs %} -{% from "components/fieldset/_macro.njk" import onsFieldset %} -{% from "components/button/_macro.njk" import onsButton %} - -{% set page_title = "Change email address conflict" %} -{% set breadcrumbsData = [ - { - "text": "Surveys", - "url": "/surveys/todo", - "id": "b-item-1" - }, - { - "text": "Account", - "url": "/my-account", - "id": "b-item-2" - }, - { - "text": "Change your contact details", - "url": "/my-account/change-account-details", - "id": "b-item-3" - }, -] %} - -{% block breadcrumbs %} - {{ - onsBreadcrumbs({ - "ariaLabel": "Breadcrumbs", - "id": "breadcrumbs", - "itemsList": breadcrumbsData - }) - }} -{% endblock breadcrumbs %} - -{% block main %} -

We are not able to change your email address

-

This is because the new address you have provided is being used on another account in our system.

-

If you are no longer required to respond to your surveys, you can transfer surveys to a colleague instead.

-

If you need further help, please contact us.

-{% endblock main %} diff --git a/frontstage/templates/account/account-change-email-address.html b/frontstage/templates/account/account-change-email-address.html deleted file mode 100644 index fa0133ff1..000000000 --- a/frontstage/templates/account/account-change-email-address.html +++ /dev/null @@ -1,87 +0,0 @@ -{% extends "layouts/_block_content.html" %} -{% from "components/breadcrumbs/_macro.njk" import onsBreadcrumbs %} -{% from "components/fieldset/_macro.njk" import onsFieldset %} -{% from "components/button/_macro.njk" import onsButton %} - -{% set page_title = "Change email address" %} -{% set breadcrumbsData = [ - { - "text": "Surveys", - "url": "/surveys/todo", - "id": "b-item-1" - }, - { - "text": "Account", - "url": "/my-account", - "id": "b-item-2" - }, - { - "text": "Change your contact details", - "url": "/my-account/change-account-details", - "id": "b-item-3" - }, -] %} - -{% block breadcrumbs %} - {{ - onsBreadcrumbs({ - "ariaLabel": "Breadcrumbs", - "id": "breadcrumbs", - "itemsList": breadcrumbsData - }) - }} -{% endblock breadcrumbs %} - -{% block main %} - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - {% if category == "error" %} - {% call onsPanel({ - "variant": 'error', - "id": 'error-id', - "iconType": 'lock', - "classes": 'ons-u-mb-m' - }) %} -

{{ message }}

- {% endcall %} - {% else %} - {% call onsPanel({ - "variant": 'success', - "id": 'success-id', - "iconType": 'check', - "classes": 'ons-u-mb-m' - }) %} -

{{ message }}

- {% endcall %} - {% endif %} - {% endfor %} - {% endif %} - {% endwith %} -

Change email address

-

You will need to authorise a change of email address.

-

We will send a verification email to {{ new_email }}.

- {% call onsPanel({}) %} -

- If you are longer required to respond to your surveys, you can transfer your surveys to a colleague. -

- {% endcall %} -
- {{ form.csrf_token }} - -
-
- {{ - onsButton({ - "text": "Send verification email", - "id": "btn-option-send-email", - "submitType": "timer" - }) - }} -
-
- Cancel -
-
-
-{% endblock main %} diff --git a/frontstage/templates/account/account-contact-detail-change.html b/frontstage/templates/account/account-contact-detail-change.html index dec468bcb..852586351 100644 --- a/frontstage/templates/account/account-contact-detail-change.html +++ b/frontstage/templates/account/account-contact-detail-change.html @@ -8,14 +8,9 @@ {% set page_title = "Change contact details" %} {% set breadcrumbsData = [ { - "text": "Surveys", - "url": "/surveys/todo", - "id": "b-item-1" - }, - { - "text": "Account", + "text": "Back", "url": "/my-account", - "id": "b-item-2" + "id": "b-item-1" } ] %} @@ -31,25 +26,18 @@ {% block main %} {% if errors|length > 0 %} - {% if errors|length == 1 %} - {% set errorTitle = 'There is 1 error on this page' %} - {% elif errors|length > 1 %} - {% set errorTitle = 'There are ' ~ errors|length ~ ' errors on this page' %} - {% endif %} {% call onsPanel({ "variant": "error", "classes": "ons-u-mb-s", - "title": errorTitle + "title": "There is a problem with your answer" }) %} -

These must be corrected to continue.

{% set errorData = [] %} {% for error in errors %} - {% set error_text = error %} {% do errorData.append( { - "text": 'Problem with the ' ~ error_text.replace('_', ' '), + "text": error[0], "url": "#" ~ error ~ '_error', "classes": "ons-js-inpagelink" } @@ -117,6 +105,22 @@

Enter your new contact details

"value": emailAddress, }) }} + {% if errors.email_address %} + {% set confirmErrorEmailAddress = { "text": errors['confirm_email_address'][0], "id": 'confirm_email_address_error' } %} + {% set confirmEmailAddress = form.confirm_email_address.data %} + {% endif %} + {{ + onsInput({ + "id": "confirm_email_address", + "name": "confirm_email_address", + "type": "text", + "label": { + "text": 'Confirm email address' + }, + "error": errorConfirmEmailAddress, + "value": confirmEmailAddress, + }) + }} {% set phoneNumber = respondent.telephone %} {% if errors.phone_number %} {% set errorPhoneNumber = { "text": errors['phone_number'][0], "id": 'phone_number_error' } %} @@ -130,7 +134,8 @@

Enter your new contact details

"autocomplete": "tel", "classes": "input--w-8", "label": { - "text": 'Phone number' + "text": "Phone number", + "description": "We will use this number if we need to contact you about your answers.
For international numbers include the country code." }, "error": errorPhoneNumber, "value": phoneNumber @@ -139,20 +144,11 @@

Enter your new contact details

{{ onsButton({ - "text": "Save", + "text": "Change details", "id": "btn-option-save", "submitType": "timer" }) }} - {{ - onsButton({ - "id": "btn-account-change-detail-cancel", - "url": url_for('account_bp.account'), - "text": 'Cancel', - "variants": 'secondary', - "noIcon": true - }) - }}
{% endblock main %} diff --git a/frontstage/templates/account/account.html b/frontstage/templates/account/account.html index 3367ae61a..c5703bc56 100644 --- a/frontstage/templates/account/account.html +++ b/frontstage/templates/account/account.html @@ -6,8 +6,40 @@ {% from "components/details/_macro.njk" import onsDetails %} {% block main %} -{% include "radio-option-select-error-panel.html" %}
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + {% if category == "error" %} + {% call onsPanel({ + "variant": 'error', + "id": 'error-id', + "iconType": 'lock', + "classes": 'ons-u-mb-m' + }) %} +

{{ message }}

+ {% endcall %} + {% elif category == "warn" %} + {% call onsPanel({ + "variant": 'warn', + "id": 'info-id', + "classes": 'ons-u-mb-m' + }) %} +

{{ message }}

+ {% endcall %} + {% else %} + {% call onsPanel({ + "variant": 'success', + "id": 'success-id', + "iconType": 'check', + "classes": 'ons-u-mb-m' + }) %} +

{{ message }}

+ {% endcall %} + {% endif %} + {% endfor %} + {% endif %} + {% endwith %}

My Account

{{ onsDescriptionList({ diff --git a/frontstage/views/account/account.py b/frontstage/views/account/account.py index f99683b24..c2c270f30 100644 --- a/frontstage/views/account/account.py +++ b/frontstage/views/account/account.py @@ -8,12 +8,7 @@ from frontstage.common.utilities import obfuscate_email from frontstage.controllers import auth_controller, party_controller from frontstage.exceptions.exceptions import ApiError, AuthError -from frontstage.models import ( - ChangePasswordFrom, - ConfirmEmailChangeForm, - ContactDetailsChangeForm, - OptionsForm, -) +from frontstage.models import ChangePasswordFrom, ContactDetailsChangeForm, OptionsForm from frontstage.views.account import account_bp from frontstage.views.template_helper import render_template @@ -96,14 +91,11 @@ def change_password(session): return render_template("account/account-change-password.html", session=session, form=form, errors=errors) -@account_bp.route("/change-account-email-address", methods=["POST"]) -@jwt_authorization(request) -def change_email_address(session): - form = ConfirmEmailChangeForm(request.values) +def change_email_address(session, new_email_address): party_id = session.get_party_id() respondent_details = party_controller.get_respondent_party_by_id(party_id) respondent_details["email_address"] = respondent_details["emailAddress"] - respondent_details["new_email_address"] = form["email_address"].data + respondent_details["new_email_address"] = new_email_address respondent_details["change_requested_by_respondent"] = True logger.info("Attempting to update email address changes on the account", party_id=party_id) try: @@ -113,11 +105,11 @@ def change_email_address(session): logger.error("Failed to updated email on account", status=exc.status_code, party_id=party_id) if exc.status_code == 409: logger.info("The email requested already registered in our system. Request denied", party_id=party_id) - return render_template("account/account-change-email-address-conflict.html") + return False else: raise exc logger.info("Successfully updated email on account", party_id=party_id) - return render_template("account/account-change-email-address-almost-done.html", session=session) + return True @account_bp.route("/change-account-details", methods=["GET", "POST"]) @@ -142,16 +134,21 @@ def change_account_details(session): logger.error("Failed to updated account", status=exc.status_code) raise exc logger.info("Successfully updated account", party_id=party_id) - success_panel = create_success_message(attributes_changed, "We have updated your ") - flash(success_panel) is_email_update_required = form["email_address"].data != respondent_details["emailAddress"] if is_email_update_required: - return render_template( - "account/account-change-email-address.html", - new_email=form["email_address"].data, - form=ConfirmEmailChangeForm(), - ) - return redirect(url_for("surveys_bp.get_survey_list", tag="todo")) + if not change_email_address(session=session, new_email_address=form["email_address"].data): + form.errors["email_address"] = ["Email address has already been used to register an account"] + return render_template( + "account/account-contact-detail-change.html", + session=session, + form=form, + errors=form.errors, + respondent=respondent_details, + ) + else: + flash("We have sent you an email to confirm your new email address.") + flash("Your contact details have changed") + return redirect(url_for("account_bp.account")) else: return render_template( "account/account-contact-detail-change.html", @@ -184,28 +181,3 @@ def check_attribute_change(form, attributes_changed, respondent_details, update_ update_required_flag = True attributes_changed.append("telephone number") return update_required_flag - - -def create_success_message(attr, message): - """ - Takes a string as message and a list of strings attr - to append message with attributes adding ',' and 'and' - - for example: if message = "I ate " - and attr = ["apple","banana","grapes"] - result will be = "I ate apple, banana and grapes." - - :param attr: list of string - :param message: string - :returns: A string formatted using the two supplied variables - :rtype: str - """ - for x in attr: - if x == attr[-1] and len(attr) >= 2: - message += " and " + x - elif x != attr[0] and len(attr) > 2: - message += ", " + x - else: - message += x - - return message diff --git a/tests/integration/views/account/test_account.py b/tests/integration/views/account/test_account.py index a67f45743..bcb28ff07 100644 --- a/tests/integration/views/account/test_account.py +++ b/tests/integration/views/account/test_account.py @@ -6,7 +6,6 @@ from frontstage import app from tests.integration.mocked_services import ( encoded_jwt_token, - respondent_enrolments, respondent_party, survey_list_todo, url_banner_api, @@ -72,34 +71,10 @@ def test_account_contact_details_error(self, mock_request, get_respondent_party_ get_respondent_party_by_id.return_value = respondent_party response = self.app.post("/my-account/change-account-details", data={"first_name": ""}, follow_redirects=True) - self.assertIn("There are 4 errors on this page".encode(), response.data) - self.assertIn("Problem with the first name".encode(), response.data) - self.assertIn("Problem with the phone number".encode(), response.data) - self.assertIn("Problem with the email address".encode(), response.data) - - @requests_mock.mock() - @patch("frontstage.controllers.party_controller.get_respondent_party_by_id") - @patch("frontstage.controllers.party_controller.update_account") - @patch("frontstage.controllers.party_controller.get_case_list_for_respondent") - @patch("frontstage.controllers.party_controller.get_respondent_enrolments") - def test_account_contact_details_success( - self, mock_request, get_respondent_enrolments, get_survey_list, _, get_respondent_party_by_id - ): - mock_request.get(url_banner_api, status_code=404) - get_respondent_party_by_id.return_value = respondent_party - get_survey_list.return_value = survey_list_todo - get_respondent_enrolments.return_value = respondent_enrolments - response = self.app.post( - "/my-account/change-account-details", - data={ - "first_name": "new first name", - "last_name": "new last name", - "phone_number": "8882257773", - "email_address": "example@example.com", - }, - follow_redirects=True, - ) - self.assertIn("updated your first name, last name and telephone number".encode(), response.data) + self.assertIn("There is a problem with your answer".encode(), response.data) + self.assertIn("Enter your first name".encode(), response.data) + self.assertIn("Enter your last name".encode(), response.data) + self.assertIn("Enter an email address".encode(), response.data) @requests_mock.mock() @patch("frontstage.controllers.party_controller.get_respondent_party_by_id") @@ -118,35 +93,12 @@ def test_account_change_account_email_address( "last_name": "test_account", "phone_number": "07772257773", "email_address": "exampleone@example.com", + "confirm_email_address": "exampleone@example.com", }, follow_redirects=True, ) - self.assertIn("updated your first name, last name and telephone number".encode(), response.data) - self.assertIn("Change email address".encode(), response.data) - self.assertIn("You will need to authorise a change of email address.".encode(), response.data) - self.assertIn("We will send a verification email to".encode(), response.data) - self.assertIn("exampleone@example.com".encode(), response.data) - - @requests_mock.mock() - @patch("frontstage.controllers.party_controller.get_respondent_party_by_id") - @patch("frontstage.controllers.party_controller.update_account") - @patch("frontstage.controllers.party_controller.get_case_list_for_respondent") - def test_account_change_account_email_address_almost_done( - self, mock_request, get_survey_list, update_account, get_respondent_party_by_id - ): - mock_request.get(url_banner_api, status_code=404) - get_respondent_party_by_id.return_value = respondent_party - get_survey_list.return_value = survey_list_todo - response = self.app.post( - "/my-account/change-account-email-address", - data={"email_address": "exampleone@example.com"}, - follow_redirects=True, - ) - self.assertIn("Almost done".encode(), response.data) - self.assertIn("We have sent a verification email to your new email address.".encode(), response.data) - self.assertIn("Follow the link in the email to verify the change.".encode(), response.data) - self.assertIn("Email not arrived? It may be in your junk folder.".encode(), response.data) - self.assertIn("If it does not arrive within 15 minutes, please".encode(), response.data) + self.assertIn("We have sent you an email to confirm your new email address.".encode(), response.data) + self.assertIn("Your contact details have changed".encode(), response.data) @requests_mock.mock() @patch("frontstage.controllers.party_controller.get_respondent_party_by_id") diff --git a/tests/unit/test_account.py b/tests/unit/test_account.py index 9dc707b79..f31fc6ae4 100644 --- a/tests/unit/test_account.py +++ b/tests/unit/test_account.py @@ -2,48 +2,10 @@ from werkzeug.datastructures import ImmutableMultiDict -from frontstage.views.account.account import ( - check_attribute_change, - create_success_message, -) +from frontstage.views.account.account import check_attribute_change class TestAccount(unittest.TestCase): - def test_message_success_with_three_attributes(self): - attr = ["first_name", "second_name", "third_name"] - message = "We have updated " - actual_message = create_success_message(attr, message) - expected_message = "We have updated first_name, second_name and third_name" - self.assertTrue(actual_message == expected_message) - - def test_message_success_with_two_attributes(self): - attr = ["first_name", "second_name"] - message = "We have updated " - actual_message = create_success_message(attr, message) - expected_message = "We have updated first_name and second_name" - self.assertTrue(actual_message == expected_message) - - def test_message_success_with_single_attribute(self): - attr = ["first_name"] - message = "We have updated " - actual_message = create_success_message(attr, message) - expected_message = "We have updated first_name" - self.assertTrue(actual_message == expected_message) - - def test_message_success_with_four_attributes(self): - attr = ["first_name", "second_name", "third_name", "email"] - message = "We have updated " - actual_message = create_success_message(attr, message) - expected_message = "We have updated first_name, second_name, third_name and email" - self.assertTrue(actual_message == expected_message) - - def test_message_success_with_no_attributes(self): - attr = [] - message = "We have updated " - actual_message = create_success_message(attr, message) - expected_message = "We have updated " - self.assertTrue(actual_message == expected_message) - def test_check_attribute_change_returns_true(self): first_name = FormData("something") last_name = FormData("test")