Skip to content

Commit

Permalink
Show Google Form feedback modal on Area and Explore pages
Browse files Browse the repository at this point in the history
Markup, styling, and JavaScript handling for feedback modal that only
gets shown on Area and Explore pages if the user hasn’t already skipped
or submitted it. Form is submitted, via Ajax, to a Google Form.

As part of this, I reused and refactored the CollapsableMailingListForm
from the Area page, into a general setUpCollapsable function.

I also fixed a bug where the "shared" JavaScript in home-out-esm.js
(such as the `.form-check + .conditional-fields` handling) wasn’t being
loaded on Area or Explore pages. I guess, really, we should either
rename home-out-esm.js to shared-out-esm.js, or we should break out
the parts that are intended to be used on other pages, and leave
home-out-esm.js for homepage-only stuff.

And since the feedback form gathers some personal information, I’ve
updated the Privacy Policy to explain that.
  • Loading branch information
zarino committed Jul 4, 2024
1 parent bf89bbe commit 6788cd7
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 76 deletions.
74 changes: 74 additions & 0 deletions hub/static/css/_feedback-modal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.feedback-modal {
--#{$prefix}modal-padding: 2rem;
--#{$prefix}modal-width: 550px;

@include media-breakpoint-up('lg') {
--#{$prefix}modal-padding: 2.5rem;
--#{$prefix}modal-width: 600px;
@include font-size(1.125rem);
}

.modal-header {
border-bottom: none;
align-items: center;
padding: var(--#{$prefix}modal-padding) var(--#{$prefix}modal-padding) 0 var(--#{$prefix}modal-padding);
}

.modal-body {
padding: 1rem var(--#{$prefix}modal-padding);
}

.modal-footer {
border-top: none;
justify-content: flex-start;
padding: 0 var(--#{$prefix}modal-padding) var(--#{$prefix}modal-padding) var(--#{$prefix}modal-padding);

& > * {
margin: 0;
}
}

.close {
padding: 1rem 1rem;
margin: -1rem -1rem -1rem auto;
background: transparent;
border: none;
color: $text-muted;
font-size: 1rem;
}

legend {
font-size: 1em; // match paragraphs
margin-bottom: 1em; // match paragraphs
}

.form-check {
margin-bottom: 0.25em;
}
}

.conditional-fields {
border-left: 4px solid $gray-400;
padding-left: 1rem;
margin-left: 0.4em;
margin-top: 1rem;
margin-bottom: 1rem;
}

.team-avatars {
display: flex;
margin: 0 -4px 1rem -4px;
}

.team-avatars__person {
display: flex;
align-items: center;
margin-right: -10px;

img {
width: 60px;
height: 60px;
border-radius: 30px;
border: 4px solid #fff;
}
}
3 changes: 3 additions & 0 deletions hub/static/css/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ $btn-font-weight: bold;

// Forms

$form-text-margin-top: 0.5rem;

$form-label-margin-bottom: 0.5rem;
$form-label-font-weight: bold;

// Form validation
Expand Down
1 change: 1 addition & 0 deletions hub/static/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@
@import "accounts";
@import "landing-page";
@import "print";
@import "feedback-modal";
Binary file added hub/static/img/avatar-alice.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hub/static/img/avatar-julia.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hub/static/img/avatar-struan.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hub/static/img/avatar-zarino.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 56 additions & 1 deletion hub/static/js/area.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import $ from 'jquery/dist/jquery.slim'
import { Chart, BarController, BarElement, CategoryScale, LinearScale, Legend, Tooltip } from 'chart.js'
import trackEvent from './analytics.esm.js'
import Collapse from 'bootstrap/js/dist/collapse'
import setUpCollapsable from './collapsable.esm.js'
import Dropdown from 'bootstrap/js/dist/dropdown'

Chart.register( BarController, BarElement, CategoryScale, LinearScale, Legend, Tooltip);
Expand All @@ -16,6 +16,20 @@ Chart.defaults.responsive = true

import setUpAreaPage from './area.esm.js'

async function mailingListSignup($form) {
const response = await fetch($form.attr('action'), {
method: $form.attr('method') || 'GET',
mode: 'cors',
credentials: 'same-origin',
body: $form.serialize(),
headers: {
"Content-Type": 'application/x-www-form-urlencoded',
"Accept": 'application/json; charset=utf-8',
},
})
return response.json()
}

$(function(){
if( 'geolocation' in navigator ) {
$('.js-geolocate').removeClass('d-none');
Expand Down Expand Up @@ -95,6 +109,47 @@ $(function(){
window.location.href = href
})
})

$('.js-collapsable-mailing-list-form').each(function(){
setUpCollapsable(
$(this).find('.js-mailing-list-name, .js-mailing-list-extras'),
$(this).find('.js-mailing-list-email input#email'),
'keyup change',
function($targets, $triggers){
return $triggers.eq(0).val() !== '';
}
);
});

$('.js-mailing-list-signup').on('submit', function(e){
e.preventDefault();
var $form = $(this);
$('.invalid-feedback').remove()
mailingListSignup($form).then(function(response){
if (response['response'] == 'ok') {
$form.hide()
$('.js-mailing-list-success').removeClass('d-none')
} else {
console.log(response)
for (var k in response["errors"]) {
var id = '#' + k
var el = $(id)
el.addClass('is-invalid')
var error_el = $('<div>')
error_el.addClass('invalid-feedback d-block fs-6 mt-2')
error_el.html( '<p>' + response["errors"][k].join(", ") + '</p>' )
el.after(error_el)
}

if ("mailchimp" in response["errors"]) {
var error_el = $('<div>')
error_el.addClass('invalid-feedback d-block fs-6 mt-2')
error_el.html( '<p>There was a problem signing you up, please try again.</p>' )
$form.before(error_el)
}
}
});
})
})

var makeChart = function() {
Expand Down
27 changes: 27 additions & 0 deletions hub/static/js/collapsable.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import $ from 'jquery/dist/jquery.slim'
import Collapse from 'bootstrap/js/dist/collapse'

const setUpCollapsable = function(targets, triggers, triggerEvents, showTest) {
var instances = [];
var $targets = $(targets); // targets can either be a jQuery object or selector(s)
var $triggers = $(triggers); // triggers can either be a jQuery object or selector(s)

var updateUI = function() {
var show = showTest($targets, $triggers);
$.each(instances, function(i, instance){
show ? instance.show() : instance.hide();
});
};

$targets.addClass('collapse').each(function(){
instances.push(new Collapse(this, { toggle: false}));
});

$triggers.on(triggerEvents, function(){
updateUI();
});

updateUI();
}

export default setUpCollapsable
2 changes: 0 additions & 2 deletions hub/static/js/explore.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
import Collapse from 'bootstrap/js/dist/collapse'

import exploreApp from './explore.esm.js'
exploreApp.mount('#exploreApp')
145 changes: 74 additions & 71 deletions hub/static/js/home.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,7 @@
import $ from 'jquery/dist/jquery.slim'
import Collapse from 'bootstrap/js/dist/collapse'
import Modal from 'bootstrap/js/dist/modal'
import trackEvent from './analytics.esm.js'

async function mailingListSignup($form) {
const response = await fetch($form.attr('action'), {
method: $form.attr('method') || 'GET',
mode: 'cors',
credentials: 'same-origin',
body: $form.serialize(),
headers: {
"Content-Type": 'application/x-www-form-urlencoded',
"Accept": 'application/json; charset=utf-8',
},
})
return response.json()
}

var setUpCollapsableMailingListForm = function() {
var $form = $(this);
var selectors = '.js-mailing-list-name, .js-mailing-list-extras';
var trigger = '.js-mailing-list-email input#email';
var instances = [];

var updateUI = function() {
var emailEntered = $(trigger).val() !== '';
$.each(instances, function(i, instance){
emailEntered ? instance.show() : instance.hide();
});
};

$(selectors, $form).addClass('collapse').each(function(){
instances.push(new Collapse(this, { toggle: false }));
});
$(trigger, $form).on('keyup change', function(){
updateUI();
});
updateUI();
};
import setUpCollapsable from './collapsable.esm.js'

$(function(){
if( 'geolocation' in navigator ) {
Expand Down Expand Up @@ -88,16 +53,6 @@ $(function(){
}
})

$('.js-email-your-mp').on('click', function(e){
e.preventDefault()
let href = $(this).attr('href')
trackEvent('email_your_mp', {
area_name: $('#area_name').text(),
area_mp_name: $('#mp_name').text()
}).always(function(){
window.location.href = href
})
})
$('.js-landingpage-search').on('submit', function(e){
var $form = $(this);
if ( ! $form.data('submitRecorded') ) {
Expand All @@ -122,35 +77,83 @@ $(function(){
});
})

$('.js-collapsable-mailing-list-form').each(setUpCollapsableMailingListForm);
$('.form-check + .conditional-fields').each(function(){
var $target = $(this); // the .conditional-fields element
var inputSetName = $target.prev().find('input').eq(0).attr('name');
var $triggers = $('input[name="' + inputSetName + '"]');

$('.js-mailing-list-signup').on('submit', function(e){
e.preventDefault();
var $form = $(this);
$('.invalid-feedback').remove()
mailingListSignup($form).then(function(response){
if (response['response'] == 'ok') {
$form.hide()
$('.js-mailing-list-success').removeClass('d-none')
} else {
console.log(response)
for (var k in response["errors"]) {
var id = '#' + k
var el = $(id)
el.addClass('is-invalid')
var error_el = $('<div>')
error_el.addClass('invalid-feedback d-block fs-6 mt-2')
error_el.html( '<p>' + response["errors"][k].join(", ") + '</p>' )
el.after(error_el)
// jQuery can't see custom bootstrap events, so use .addEventListener instead
$target[0].addEventListener('shown.bs.collapse', function(){
$target.find('input').eq(0).trigger('focus');
});

setUpCollapsable(
$target,
$triggers,
'change',
function($target, $triggers){
return $target.prev().find('input').eq(0).prop('checked');
}
);
});

$('.feedback-modal').each(function(){
var modal = new Modal(this);

var shouldShowModal = function() {
if ( ! window.localStorage ) {
// Can’t trigger modal, because no localStorage support.
return false;
}
if ( localStorage.getItem('submitted-feedback-modal-timestamp') ) {
// Browser has already submitted the feedback form.
return false;
}
if ( localStorage.getItem('skipped-feedback-modal-timestamp') ) {
// Respect skipped modals for 7 days.
var skippedTimestamp = localStorage.getItem('skipped-feedback-modal-timestamp');
var nowTimestamp = (new Date()).getTime() / 1000;
var coolingOffPeriod = 60 * 60 * 24 * 7;
if ( nowTimestamp - skippedTimestamp < coolingOffPeriod ) {
return false;
}
}
return true;
}

this.addEventListener('hide.bs.modal', event => {
if ( ! localStorage.getItem('submitted-feedback-modal-timestamp') ) {
var timestamp = (new Date()).getTime() / 1000;
localStorage.setItem('skipped-feedback-modal-timestamp', timestamp);
}
});

if ("mailchimp" in response["errors"]) {
var error_el = $('<div>')
error_el.addClass('invalid-feedback d-block fs-6 mt-2')
error_el.html( '<p>There was a problem signing you up, please try again.</p>' )
$form.before(error_el)
if ( shouldShowModal() ) {
localStorage.removeItem('skipped-feedback-modal-timestamp');
modal.show();
}

this.addEventListener('submit', function(e){
e.preventDefault();
let form = this.querySelector('form');
let params = new URLSearchParams(Array.from(new FormData(form))).toString()

fetch(form.action + '?' + params, {
method: form.method,
mode: 'no-cors',
cache: 'no-cache',
credentials: 'omit',
headers: {
"Content-Type": 'application/x-www-form-urlencoded'
}
});

if ( window.localStorage ) {
var timestamp = (new Date()).getTime() / 1000;
localStorage.setItem('submitted-feedback-modal-timestamp', timestamp);
}

modal.hide();
});
})
})
3 changes: 3 additions & 0 deletions hub/templates/hub/area.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{% block bodyclass %}js-area-page{% endblock %}

{% block script %}
<script type="module" src="{% static 'js/home-out-esm.js' %}"></script>
<script type="module" src="{% static 'js/area-out-esm.js' %}"></script>
{% endblock %}

Expand Down Expand Up @@ -693,4 +694,6 @@ <h2 class="h4">Climate Coalition member?</h2>
</div>
</aside>

{% include 'hub/includes/feedback-modal.html' %}

{% endblock %}
Loading

0 comments on commit 6788cd7

Please sign in to comment.