From e6025dc658c2164ce27842a8db9bb57ce616ebec Mon Sep 17 00:00:00 2001 From: Catalin Date: Thu, 13 Jun 2024 17:37:17 +0300 Subject: [PATCH] HDX-9545 build page C2 - re-enter email address --- .../ui_constants/onboarding/__init__.py | 2 + .../ui_constants/onboarding/change_email.py | 18 ++++ .../ui_constants/onboarding/verify_email.py | 4 +- .../onboarding/signup/change-email.html | 42 +++++++++ .../onboarding/signup/verify-email.html | 4 + .../ckanext/hdx_users/logic/schema.py | 9 ++ .../ckanext/hdx_users/views/onboarding.py | 86 ++++++++++++++++++- 7 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/change_email.py create mode 100644 ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/change-email.html diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/__init__.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/__init__.py index f014d2a4d0..7c2eca3aae 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/__init__.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/__init__.py @@ -1,6 +1,7 @@ from ckanext.hdx_theme.helpers.ui_constants.onboarding.user_info import CONSTANTS as USER_INFO_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.onboarding.value_proposition import CONSTANTS as VALUE_PROPOSITION_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.onboarding.verify_email import CONSTANTS as VERIFY_EMAIL_CONSTANTS +from ckanext.hdx_theme.helpers.ui_constants.onboarding.change_email import CONSTANTS as CHANGE_EMAIL_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.onboarding.account_validated import CONSTANTS as ACCOUNT_VALIDATED_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.onboarding.request_join_organisation import CONSTANTS as REQUEST_JOIN_ORGANISATION_CONSTANTS from ckanext.hdx_theme.helpers.ui_constants.onboarding.confirm_organisation_choice import CONSTANTS as CONFIRM_ORGANISATION_CHOICE_CONSTANTS @@ -15,6 +16,7 @@ 'USER_INFO': USER_INFO_CONSTANTS, 'VALUE_PROPOSITION': VALUE_PROPOSITION_CONSTANTS, 'VERIFY_EMAIL': VERIFY_EMAIL_CONSTANTS, + 'CHANGE_EMAIL': CHANGE_EMAIL_CONSTANTS, 'ACCOUNT_VALIDATED': ACCOUNT_VALIDATED_CONSTANTS, 'REQUEST_JOIN_ORGANISATION': REQUEST_JOIN_ORGANISATION_CONSTANTS, 'CONFIRM_ORGANISATION_CHOICE': CONFIRM_ORGANISATION_CHOICE_CONSTANTS, diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/change_email.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/change_email.py new file mode 100644 index 0000000000..cb4ec111a0 --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/change_email.py @@ -0,0 +1,18 @@ +from ckanext.hdx_theme.helpers.ui_constants.onboarding.user_info import CONSTANTS as USER_INFO_CONSTANTS + +CONSTANTS = { + 'STEPS_1': USER_INFO_CONSTANTS['STEPS_1'], + 'STEPS_2': USER_INFO_CONSTANTS['STEPS_2'], + 'STEPS_3': USER_INFO_CONSTANTS['STEPS_3'], + + 'PAGE_TITLE': '''Re-enter your email address''', + + 'INPUT_EMAIL_LABEL': '''Enter your email address''', + 'INPUT_EMAIL_PLACEHOLDER': USER_INFO_CONSTANTS['INPUT_EMAIL_PLACEHOLDER'], + 'INPUT_EMAIL_ERROR': USER_INFO_CONSTANTS['INPUT_EMAIL_ERROR'], + 'INPUT_EMAIL2_LABEL': USER_INFO_CONSTANTS['INPUT_EMAIL2_LABEL'], + 'INPUT_EMAIL2_PLACEHOLDER': USER_INFO_CONSTANTS['INPUT_EMAIL2_PLACEHOLDER'], + 'INPUT_EMAIL2_ERROR': USER_INFO_CONSTANTS['INPUT_EMAIL2_ERROR'], + + 'BUTTON_SUBMIT': '''Next''', +} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/verify_email.py b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/verify_email.py index 1ef9bf23f1..7f9a5280dd 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/verify_email.py +++ b/ckanext-hdx_theme/ckanext/hdx_theme/helpers/ui_constants/onboarding/verify_email.py @@ -7,5 +7,7 @@ 'PAGE_TITLE': '''Verify your email address''', 'BODY_MAIN_TEXT': '''We have sent an email to {0} so that you can verify your email address. If you don’t see the email, please check your spam or junk folder.''', - 'BODY_MAIN_TEXT_WITHOUT_EMAIL': '''We have sent an email so that you can verify your email address. If you don’t see the email, please check your spam or junk folder.''' + 'BODY_MAIN_TEXT_WITHOUT_EMAIL': '''We have sent an email so that you can verify your email address. If you don’t see the email, please check your spam or junk folder.''', + + 'CHANGE_EMAIL_TEXT': '''If you need to change your email address, click here''', } diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/change-email.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/change-email.html new file mode 100644 index 0000000000..0fda8ce6dd --- /dev/null +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/change-email.html @@ -0,0 +1,42 @@ +{% extends "onboarding/base.html" %} + +{% block scripts %} + {{ super() }} + {% asset 'hdx_theme/hdx-form-validator' %} +{% endblock %} + +{% set CONST = h.HDX_CONST('UI_CONSTANTS')['ONBOARDING']['CHANGE_EMAIL'] %} + +{% block subtitle %}{{ _('Re-enter email address') }}{% endblock %} + +{% block breadcrumb_content %} + +{% endblock %} + +{% block content %} + +
+ +
+
+ {{ h.snippet('bem.blocks/stepper.html', steps=[CONST.STEPS_1, CONST.STEPS_2, CONST.STEPS_3], spacing_class="my-5", current_step=2) }} +
+ {{ h.snippet('bem.blocks/heading.html', title=CONST.PAGE_TITLE, spacing_class="mb-4") }} +
+
+
+ +
+
+
+ {{ h.csrf_input() }} + {{ h.snippet('bem.blocks/input_field.html', type="email", required=True, autocomplete='new-email', label=CONST.INPUT_EMAIL_LABEL, data_attributes={"validation": "email", "validation-error": CONST.INPUT_EMAIL_ERROR}, name="email", errors=errors.get('email'), placeholder=CONST.INPUT_EMAIL_PLACEHOLDER, spacing_class="mb-4") }} + {{ h.snippet('bem.blocks/input_field.html', type="email", required=True, autocomplete='new-email', label=CONST.INPUT_EMAIL2_LABEL, data_attributes={"validation": "email,match", "validation-match": "email", "validation-error": CONST.INPUT_EMAIL2_ERROR}, name="email2", errors=errors.get('email2'), placeholder=CONST.INPUT_EMAIL2_PLACEHOLDER, spacing_class="mb-4") }} + {{ h.snippet('bem.blocks/form_button.html', type="submit", title=CONST.BUTTON_SUBMIT, disabled=True, button_classes=["form-button__btn_font-size_big", "btn-primary", "btn-lg", "d-block", "w-100"]) }} +
+
+
+ +
+{% endblock %} diff --git a/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/verify-email.html b/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/verify-email.html index 4b15b4bddc..25e794440b 100644 --- a/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/verify-email.html +++ b/ckanext-hdx_theme/ckanext/hdx_theme/templates/onboarding/signup/verify-email.html @@ -24,6 +24,10 @@ {{ h.snippet('bem.blocks/heading.html', title=CONST.PAGE_TITLE, spacing_class="mb-5") }} {% set main_text = CONST.BODY_MAIN_TEXT.format(session.get('user_info_email')) if session.get('user_info_email') else CONST.BODY_MAIN_TEXT_WITHOUT_EMAIL %} {{ h.snippet('bem.blocks/paragraph.html', text=main_text, paragraph_classes=["paragraph__text_font-size-big"], spacing_class="mb-5") }} + {% if session.get('user_info_id') %} + {{ h.snippet('bem.blocks/paragraph.html', text='--', spacing_class="mb-5") }} + {{ h.snippet('bem.blocks/paragraph.html', text=CONST.CHANGE_EMAIL_TEXT.format(h.url_for('hdx_user_onboarding.change_email')), spacing_class="mb-5") }} + {% endif %} diff --git a/ckanext-hdx_users/ckanext/hdx_users/logic/schema.py b/ckanext-hdx_users/ckanext/hdx_users/logic/schema.py index 7383ed74a0..cc6b0c709c 100644 --- a/ckanext-hdx_users/ckanext/hdx_users/logic/schema.py +++ b/ckanext-hdx_users/ckanext/hdx_users/logic/schema.py @@ -98,3 +98,12 @@ def onboarding_user_edit_form_schema(not_empty, strip_value, user_email_validato schema['email'] = [not_empty, strip_value, user_email_validator, unicode_safe] return schema + + +@validator_args +def onboarding_user_change_email_form_schema(not_empty, strip_value, user_email_validator, user_emails_match): + schema = { + 'email': [not_empty, strip_value, user_email_validator, user_emails_match, unicode_safe], + 'email2': [unicode_safe], + } + return schema diff --git a/ckanext-hdx_users/ckanext/hdx_users/views/onboarding.py b/ckanext-hdx_users/ckanext/hdx_users/views/onboarding.py index 9b3d3164cf..4d8f9b208c 100644 --- a/ckanext-hdx_users/ckanext/hdx_users/views/onboarding.py +++ b/ckanext-hdx_users/ckanext/hdx_users/views/onboarding.py @@ -20,7 +20,7 @@ from ckanext.hdx_users.helpers.constants import ONBOARDING_CAME_FROM_EXTRAS_KEY, ONBOARDING_CAME_FROM_STATE_EXTRAS_KEY, \ ONBOARDING_MAILCHIMP_OPTIN_KEY from ckanext.hdx_users.views.user_view_helper import CaptchaNotValid, OnbCaptchaErr, error_message -from ckanext.hdx_users.logic.schema import onboarding_user_new_form_schema +from ckanext.hdx_users.logic.schema import onboarding_user_new_form_schema, onboarding_user_change_email_form_schema log = logging.getLogger(__name__) @@ -151,6 +151,7 @@ def post(self) -> Union[Response, str]: validation_link=h.url_for('hdx_user_onboarding.validate_account', token=token['token'], qualified=True) ) + session['user_info_id'] = user_dict.get('id') session['user_info_email'] = user_dict.get('email') return redirect('hdx_user_onboarding.verify_email', user_id=user_dict.get('id')) @@ -196,6 +197,85 @@ def verify_email(user_id: str) -> str: return abort(404, _(u'Page not found')) +def change_email() -> str: + if session.get('user_info_id'): + user_id = session.get('user_info_id') + + try: + context = { + 'model': model, + 'session': model.Session, + 'schema': onboarding_user_change_email_form_schema(), + 'keep_email': True, + 'ignore_auth': True + } + + user_dict = get_action('user_show')(context, {'id': user_id}) + + is_user_validated_and_token_disabled = tokens.is_user_validated_and_token_disabled(user_dict) + if is_user_validated_and_token_disabled: + return redirect('hdx_user_onboarding.validated_account', user_id=user_id) + + if request.method == 'POST': + try: + data_dict = logic.clean_dict( + dictization_functions.unflatten(logic.tuplize_dict(logic.parse_params(request.form)))) + except dictization_functions.DataError: + abort(400, _(u'Integrity Error')) + + # email update + try: + updated_user = get_action('user_patch')(context, { + 'id': user_dict['id'], + 'email': data_dict.get('email'), + 'email2': data_dict.get('email2'), + }) + + old_token = tokens.token_show(context, user_dict) + new_token = tokens.refresh_token(context, old_token) + subject = h.HDX_CONST('UI_CONSTANTS')['ONBOARDING']['EMAIL_SUBJECTS']['EMAIL_CONFIRMATION'] + tokens.send_validation_email( + updated_user, + new_token, + subject, + 'email/content/onboarding/email_confirmation.html', + validation_link=h.url_for('hdx_user_onboarding.validate_account', token=new_token['token'], + qualified=True) + ) + + session['user_info_email'] = updated_user.get('email') + return redirect('hdx_user_onboarding.verify_email', user_id=user_dict.get('id')) + except NotAuthorized: + log.error(f'Unauthorized to change email address for user "{user_id}"') + abort(403, _(u'Unauthorized to edit user %s') % user_id) + except NotFound: + log.error(f'User "{user_id}" could not be found to change email address') + abort(404, _(u'User not found')) + except ValidationError as e: + errors = e.error_dict + error_summary = e.error_summary + template_data = { + u'errors': errors, + u'error_summary': error_summary, + } + return render('onboarding/signup/change-email.html', template_data) + except Exception as e: + log.error(e) + abort(404, _(u'Something went wrong. Please contact support')) + + template_data = { + u'errors': {}, + u'error_summary': {}, + } + return render('onboarding/signup/change-email.html', template_data) + + except NotFound: + log.error(f'User "{user_id}" could not be found to change email address') + abort(404, _(u'User not found')) + + return abort(404, _(u'Page not found')) + + def validate_account(token: str) -> str: if request.user_agent.string.strip() and request.method == 'GET': # we don't want to run this for 'HEAD' requests or for requests that don't come from a browser @@ -233,6 +313,8 @@ def validate_account(token: str) -> str: if session.get('user_info_email'): session.pop('user_info_email') + if session.get('user_info_id'): + session.pop('user_info_id') template_data = { 'fullname': user_dict.get('fullname', '') @@ -269,6 +351,8 @@ def validated_account(user_id: str) -> str: methods=[u'GET', u'POST'], strict_slashes=False) hdx_user_onboarding.add_url_rule(u'/verify-email//', view_func=verify_email, methods=[u'GET'], strict_slashes=False) +hdx_user_onboarding.add_url_rule(u'/change-email/', view_func=change_email, methods=[u'GET', u'POST'], + strict_slashes=False) hdx_user_onboarding.add_url_rule(u'/validate-account//', view_func=validate_account, methods=[u'GET'], strict_slashes=False) hdx_user_onboarding.add_url_rule(u'/validated-account//', view_func=validated_account, methods=[u'GET'],