Skip to content

Commit

Permalink
Merge pull request #2868 from uktrade/KLS-1795-inline-feedback
Browse files Browse the repository at this point in the history
KLS-1795 add inline feedback component
  • Loading branch information
timothyPatterson authored Feb 2, 2024
2 parents 8779505 + 9f6c872 commit eb262e9
Show file tree
Hide file tree
Showing 36 changed files with 651 additions and 73 deletions.
2 changes: 2 additions & 0 deletions contact/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
FeedbackFormView,
FTASubscribeFormView,
GuidanceView,
InlineFeedbackView,
InternationalFormView,
InternationalSuccessView,
OfficeContactFormView,
Expand Down Expand Up @@ -268,6 +269,7 @@
},
name='contact-free-trade-agreements-success',
),
path('contact/inline-feedback', skip_ga360(InlineFeedbackView.as_view()), name='contact-inline-feedback'),
]

if settings.FEATURE_DIGITAL_POINT_OF_ENTRY:
Expand Down
52 changes: 52 additions & 0 deletions contact/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import pickle
from http.client import CREATED
from urllib.parse import urlparse

from directory_forms_api_client import actions
from directory_forms_api_client.helpers import FormSessionMixin, Sender
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.template.response import TemplateResponse
Expand All @@ -14,6 +16,7 @@
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from formtools.wizard.views import NamedUrlSessionWizardView
from rest_framework.generics import GenericAPIView

from contact import constants, forms as contact_forms, helpers, mixins as contact_mixins
from core import mixins as core_mixins, snippet_slugs
Expand Down Expand Up @@ -1130,3 +1133,52 @@ def get_context_data(self, **kwargs):
)
context['privacy_url'] = PRIVACY_POLICY_URL__CONTACT_TRIAGE_FORMS_SPECIAL_PAGE
return context


class InlineFeedbackView(GenericAPIView):
def post(self, request, *args, **kwargs):
js_enabled = 'js_enabled' in request.query_params.keys()
data = self.request.data.copy()

# non-js for initial yes/no form where we use query params to pass the page_useful value
if not js_enabled and 'page_useful' in request.query_params.keys():
data['page_useful'] = request.query_params['page_useful']

email_address = request.user.email if request.user.is_authenticated else '[email protected]'

sender = Sender(
email_address=email_address,
country_code=None,
)

action = actions.SaveOnlyInDatabaseAction(
full_name='NA',
email_address=email_address,
subject='NA',
sender=sender,
form_url=self.request.get_full_path(),
)

save_result = action.save(data)

if js_enabled:
response = HttpResponse()
response.status_code = save_result.status_code
return response
else:
# for non-js the user is redirected to the current page with some additional QS params that are used
# when determining which elements should be displayed and navigates the user back to #inline-feedback
if save_result.status_code == CREATED:
qs = (
f"?page_useful={data['page_useful']}"
if 'page_useful' in request.query_params.keys()
else '?detailed_feedback_submitted=True'
)
response = HttpResponseRedirect(redirect_to=f"{data['current_url']}{qs}/#inline-feedback")
else:
response = HttpResponseRedirect(
redirect_to=f"{data['current_url']}?submission_error=True/#inline-feedback"
)

response.status_code = 303
return response
82 changes: 82 additions & 0 deletions core/templates/components/inline_feedback/negative_feedback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<div>
<div class="great-inline-feedback__submission-confirmation"
id="negative-feedback-submision-confirmation"
aria-live="polite"
tabindex="-1">
<span role="img" class="fa fa-check-circle"></span>
<h3>Thanks for letting us know</h3>
</div>
<div class="govuk-form-group">
<form method="post"
action="{% url 'contact:contact-inline-feedback' %}?detailed_feedback_submitted=True"
class="great-inline-feedback__detail-form"
id="negative-feedback-form">
{% csrf_token %}
<input type="hidden" name="current_url" value="{{ request.path }}">
<input type="hidden" name="page_useful" value="False">
<fieldset class="govuk-fieldset" aria-describedby="Why was this page useful?">
<legend class="great">
<span class="great-font-bold">Can you tell us more about your feedback?</span>
</legend>
<div class="govuk-checkboxes govuk-!-padding-top-4 govuk-!-padding-bottom-4"
data-module="govuk-checkboxes">
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="did_not_find_information"
value="True"
id="did_not_find_info">
<label for="did_not_find_info" class="govuk-label govuk-checkboxes__label">
I didn't find the information I needed
</label>
</div>
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="hard_to_find"
value="True"
id="hard_to_find">
<label for="hard_to_find" class="govuk-label govuk-checkboxes__label">It was hard to find</label>
</div>
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="hard_to_understand"
value="True"
id="hard_to_understand">
<label for="hard_to_understand" class="govuk-label govuk-checkboxes__label">It was hard to understand</label>
</div>
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="looked_broken"
value="True"
id="looked_broken">
<label for="looked_broken" class="govuk-label govuk-checkboxes__label">It looked broken</label>
</div>
</div>
<div>
<label for="more-detail" class="great">
<span class="great-font-bold">Is there anything else you can tell us?</span>
</label>
<div id="more-detail-hint">
<p>Do not share any personal or commercially sensitive information.</p>
</div>
<textarea class="govuk-textarea"
id="more-detail"
name="more_detail"
rows="4"
maxlength="1000"
aria-describedby="more-detail-hint"></textarea>
</div>
</fieldset>
<span class="great-inline-feedback-detail-form__submission_buttons govuk-!-padding-top-1">
<button type="submit"
class="primary-button small-button"
id="send-feedback-no"
value="no">Send feedback</button>
<a href={{ request.path }}>Cancel</a>
</span>
</form>
</div>
</div>
81 changes: 81 additions & 0 deletions core/templates/components/inline_feedback/positive_feedback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<div>
<div class="great-inline-feedback__submission-confirmation"
aria-live="polite"
id="positive-feedback-submision-confirmation"
tabindex="-1">
<span role="img" class="fa fa-check-circle"></span>
<h3>Thanks for letting us know</h3>
</div>
<div class="govuk-form-group">
<form method="post"
action="{% url 'contact:contact-inline-feedback' %}?detailed_feedback_submitted=True"
class="great-inline-feedback__detail-form"
id="positive-feedback-form">
{% csrf_token %}
<input type="hidden" name="current_url" value="{{ request.path }}">
<input type="hidden" name="page_useful" value="True">
<fieldset class="govuk-fieldset" aria-describedby="Why was this page useful?">
<legend class="great">
<span class="great-font-bold">Can you tell us why this page was useful?</span>
</legend>
<div class="govuk-checkboxes govuk-!-padding-top-4 govuk-!-padding-bottom-4"
data-module="govuk-checkboxes">
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="found_information_needed"
value="True"
id="found-info">
<label for="found-info" class="govuk-label govuk-checkboxes__label">I found the information I needed</label>
</div>
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="easily_found"
value="True"
id="easily-found">
<label for="easily-found" class="govuk-label govuk-checkboxes__label">It was easy to find</label>
</div>
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="easily_understood"
value="True"
id="easily-understood">
<label for="easily-understood" class="govuk-label govuk-checkboxes__label">It was easy to understand</label>
</div>
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback">
<input type="checkbox"
class="govuk-checkboxes__input"
name="will_use_information_again"
value="True"
id="will-use-information-again">
<label for="will-use-information-again"
class="govuk-label govuk-checkboxes__label">I will use this information again</label>
</div>
</div>
<div>
<label for="more-detail" class="great">
<span class="great-font-bold">Is there anything else you can tell us?</span>
</label>
<div id="more-detail-hint-">
<p>Do not share any personal or commercially sensitive information.</p>
</div>
<textarea class="govuk-textarea"
id="more-detail"
name="more_detail"
rows="4"
maxlength="1000"
aria-describedby="more-detail-hint"></textarea>
</div>
</fieldset>
<span class="great-inline-feedback-detail-form__submission_buttons govuk-!-padding-top-1">
<button type="submit"
class="primary-button small-button"
id="send-feedback-yes"
value="yes">Send feedback</button>
<a href={{ request.path }}>Cancel</a>
</span>
</form>
</div>
</div>
137 changes: 137 additions & 0 deletions core/templates/components/inline_feedback/was_page_useful.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{% load show_feedback from content_tags %}
{% load get_inline_feedback_visibility from content_tags %}
{% show_feedback request.get_full_path as show_feedback %}
{% if show_feedback %}
<div class="great-full-width govuk-!-padding-top-6 govuk-!-padding-bottom-6 great-inline-feedback__container">
<div class="govuk-grid-row great-container">
<div class="govuk-grid-column-two-thirds govuk-!-padding-0"
id="inline-feedback">
{% get_inline_feedback_visibility request.get_full_path as feedback_visibility %}
<div id="submission-error"
aria-live="polite"
tabindex="-1"
class="great-inline-feedback__submission-error {% if not feedback_visibility.show_submission_error %}great-hidden{% endif %}">
<span role="img" class="fa fa-exclamation-circle"></span>
<h3>Something went wrong. Please try again.</h3>
</div>
<span id="page-useful"
class="{% if not feedback_visibility.show_page_useful %} great-hidden {% endif %}">
<form method="post"
class="great-inline-feedback__page_useful_form"
id="page-useful-form">
<h3 class="govuk-heading-xs">Was this page useful?</h3>
{% csrf_token %}
<input type="hidden" name="current_url" value="{{ request.path }}">
<button type="submit"
class="secondary-button small-button"
id="page-useful-yes"
formaction="{% url 'contact:contact-inline-feedback' %}?page_useful=True">Yes</button>
<button type="submit"
class="secondary-button small-button"
id="page-useful-no"
formaction="{% url 'contact:contact-inline-feedback' %}?page_useful=False">No</button>
</form>
</span>
<span id="positive-feedback"
class="{% if not feedback_visibility.show_positive_feedback %} great-hidden {% endif %}">
{% include 'components/inline_feedback/positive_feedback.html' %}
</span>
<span id="negative-feedback"
class="{% if not feedback_visibility.show_negative_feedback %} great-hidden {% endif %}">
{% include 'components/inline_feedback/negative_feedback.html' %}
</span>
<div id="detailed-feedback-received"
aria-live="polite"
tabindex="-1"
class="great-inline-feedback__submission-confirmation {% if not feedback_visibility.show_detailed_feedback_received %}great-hidden{% endif %}">
<span role="img" class="fa fa-check-circle"></span>
<h2>Thanks for your feedback</h2>
</div>
</div>
</div>
</div>
{% block body_js %}
<script>
const currentPage = window.location.pathname
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value

const postFeedback = async function (body){
const submitAPIEndpoint = '/contact/inline-feedback'
return await fetch(`${submitAPIEndpoint}?js_enabled=True`, {
method: 'POST',
headers: {
'Content-Type': "application/json",
'X-CSRFToken': csrfToken
},
body: JSON.stringify(body)
})
}

const hideFeedbackError = function () {
document.getElementById('submission-error').classList.add('great-hidden')
}

const handleFeedbackError = function() {
document.getElementById('submission-error').classList.remove('great-hidden')
document.getElementById('submission-error').focus()
}

const handleFeedbackSuccess = function(originalElementID, successElementID, successMessageID) {
document.getElementById(originalElementID).classList.toggle('great-hidden')
document.getElementById(successElementID).classList.toggle('great-hidden')
document.getElementById(successMessageID).focus()
}

document.getElementById('page-useful-form').addEventListener('submit', function(event){
event.preventDefault();
})

const pageUsefulButtons = [{'id':'page-useful-yes', 'followUpFormID': 'positive-feedback', 'successMessageID': 'positive-feedback-submision-confirmation'}, {'id':'page-useful-no', 'followUpFormID': 'negative-feedback', 'successMessageID': 'negative-feedback-submision-confirmation'}]

pageUsefulButtons.forEach((button)=>{
document.getElementById(button.id).addEventListener('click', async function(event){
try{
hideFeedbackError()
await postFeedback({
'current_url': currentPage,
'page_title': document.title,
'page_useful': `${button.id == 'page-useful-yes'}`,
})
handleFeedbackSuccess('page-useful', button.followUpFormID, button.successMessageID)
} catch (e) {
handleFeedbackError()
}
})
})

const detailedFeedbackIDs = [{'containerID': 'positive-feedback', 'formID': 'positive-feedback-form'}, {'containerID': 'negative-feedback', 'formID': 'negative-feedback-form'}]

detailedFeedbackIDs.forEach((feedbackType)=>{
document.getElementById(feedbackType.formID).addEventListener('submit', async function(event){
event.preventDefault();
const form = event.target
const formData = new FormData(form)

const formDataObject = {};
formData.forEach((value, key)=>{
if (key != 'csrfmiddlewaretoken') formDataObject[key] = value;
});

try {
hideFeedbackError()
await postFeedback({
'current_url': currentPage,
'page_title': document.title,
'page_useful': feedbackType.containerID == 'positive-feedback',
...formDataObject,
})
handleFeedbackSuccess(feedbackType.containerID, 'detailed-feedback-received', 'detailed-feedback-received')
} catch (e) {
handleFeedbackError()
}
})
})

</script>
{% endblock %}
{% endif %}
Loading

0 comments on commit eb262e9

Please sign in to comment.