diff --git a/.browserslistrc b/.browserslistrc
index be9bfee68..6ade378c9 100644
--- a/.browserslistrc
+++ b/.browserslistrc
@@ -1,8 +1,10 @@
> 1%
Chrome >= 50
-IE >= 11
Edge >= 14
Firefox >= 50
Opera >= 40
Safari >= 8
iOS >= 8
+# Although IE 11 shows a deprecation banner, we still support the transpilation option.
+# IE 11 support may be removed starting from 01 / 06 / 2020.
+IE >= 11
diff --git a/Gemfile.lock b/Gemfile.lock
index b516e9f39..9a923b78b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -138,7 +138,7 @@ GEM
selenium-webdriver
case_transform (0.2)
activesupport
- chartkick (3.2.0)
+ chartkick (3.3.0)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chunky_png (1.3.11)
diff --git a/README.md b/README.md
index faf159341..94a643ecf 100644
--- a/README.md
+++ b/README.md
@@ -150,7 +150,7 @@ Pour les lister : `bin/rake -D support:`.
## Compatibilité navigateurs
-L'application supporte les navigateurs récents : Firefox, Chrome, Safari, Edge et Internet Explorer 11 (voir `config/initializers/browser.rb`).
+L'application gère les navigateurs récents, parmis lequels Firefox, Chrome, Safari et Edge (voir `config/initializers/browser.rb`).
La compatibilité est testée par Browserstack.
[](https://www.browserstack.com/)
diff --git a/app/assets/stylesheets/new_design/outdated_browser_banner.scss b/app/assets/stylesheets/new_design/outdated_browser_banner.scss
index 3630c80b9..7b94f8a79 100644
--- a/app/assets/stylesheets/new_design/outdated_browser_banner.scss
+++ b/app/assets/stylesheets/new_design/outdated_browser_banner.scss
@@ -3,15 +3,23 @@
#outdated-browser-banner {
width: 100%;
- bottom: 0;
- left: 0;
margin: 0;
padding: $default-padding;
- color: #FFFFFF;
- background-color: $medium-red;
+ color: $black;
+ background-color: $yellow;
+}
- a {
- color: $lighter-blue;
- text-decoration: underline;
+.outdated-browser-icon {
+ margin-right: $default-padding;
+ font-size: 30px;
+ opacity: 0.7;
+}
+
+.outdated-browser-buttons {
+ display: flex;
+ margin-left: auto;
+
+ .button {
+ margin-left: $default-spacer;
}
}
diff --git a/app/assets/stylesheets/new_design/user_signup.scss b/app/assets/stylesheets/new_design/user_signup.scss
new file mode 100644
index 000000000..fe0b174db
--- /dev/null
+++ b/app/assets/stylesheets/new_design/user_signup.scss
@@ -0,0 +1,24 @@
+@import "colors";
+@import "constants";
+
+.suspect-email {
+ background-color: $orange-bg;
+ text-align: center;
+ margin-top: -$default-padding * 2;
+ margin-bottom: $default-padding * 2;
+ padding: ($default-padding - 2) $default-padding $default-padding $default-padding;
+ border-radius: 0 0 5px 5px;
+}
+
+.email-suggestion-address {
+ font-weight: bold;
+}
+
+.email-suggestion-title {
+ margin-bottom: $default-spacer;
+}
+
+.email-suggestion-answer button {
+ margin: 0 $default-spacer / 2;
+ min-width: 66px;
+}
diff --git a/app/assets/stylesheets/outdated_browser_banner.scss b/app/assets/stylesheets/outdated_browser_banner.scss
index 6ec1c0590..c4b2eb089 100644
--- a/app/assets/stylesheets/outdated_browser_banner.scss
+++ b/app/assets/stylesheets/outdated_browser_banner.scss
@@ -1,16 +1,25 @@
#outdated-browser-banner {
- position: fixed;
- text-align: center;
- line-height: 2em;
- color: #FFFFFF;
- background-color: #990000;
width: 100%;
margin: 0;
- bottom: 0;
- left: 0;
- z-index: 1000;
- a {
- color: #C3D9FF;
+ color: #333333;
+ background-color: #FEF3B8;
+
+ .container {
+ display: flex;
+ width: auto;
+ align-items: center;
+ padding: 8px 16px;
}
}
+
+.outdated-browser-icon {
+ margin-right: 16px;
+ font-size: 30px;
+ opacity: 0.7;
+}
+
+.outdated-browser-buttons {
+ display: flex;
+ margin-left: auto;
+}
diff --git a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb
index 620623ac5..f965781e5 100644
--- a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb
+++ b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb
@@ -61,7 +61,7 @@ module NewAdministrateur
value: bad_emails.join(', '))
end
- email_to_adds = correct_emails - groupe_instructeur.instructeurs.pluck(:email)
+ email_to_adds = correct_emails - groupe_instructeur.instructeurs.map(&:email)
if email_to_adds.present?
instructeurs = email_to_adds.map do |instructeur_email|
@@ -159,8 +159,8 @@ module NewAdministrateur
end
def available_instructeur_emails
- all = current_administrateur.instructeurs.pluck(:email)
- assigned = groupe_instructeur.instructeurs.pluck(:email)
+ all = current_administrateur.instructeurs.map(&:email)
+ assigned = groupe_instructeur.instructeurs.map(&:email)
(all - assigned).sort
end
end
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 0014c7766..bcf28f57d 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -1,4 +1,6 @@
class RootController < ApplicationController
+ include ApplicationHelper
+
def index
if administrateur_signed_in?
return redirect_to admin_procedures_path
@@ -69,4 +71,13 @@ class RootController < ApplicationController
def suivi
end
+
+ def dismiss_outdated_browser
+ dismiss_outdated_browser_banner
+
+ respond_to do |format|
+ format.html { redirect_back(fallback_location: root_path) }
+ format.js { render js: helpers.remove_element('#outdated-browser-banner') }
+ end
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0fd4c8bb2..9929ab7f6 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -120,4 +120,15 @@ module ApplicationHelper
def try_format_datetime(datetime)
datetime.present? ? I18n.l(datetime) : ''
end
+
+ def dismiss_outdated_browser_banner
+ cookies[:dismissed_outdated_browser_banner] = {
+ value: 'true',
+ expires: 1.week.from_now
+ }
+ end
+
+ def has_dismissed_outdated_browser_banner?
+ cookies[:dismissed_outdated_browser_banner] == 'true'
+ end
end
diff --git a/app/javascript/new_design/user-sign_up.js b/app/javascript/new_design/user-sign_up.js
new file mode 100644
index 000000000..c2a8c4d70
--- /dev/null
+++ b/app/javascript/new_design/user-sign_up.js
@@ -0,0 +1,35 @@
+import { delegate, show, hide } from '@utils';
+import { suggest } from 'email-butler';
+
+const userNewEmailSelector = '#new_user input#user_email';
+const passwordFieldSelector = '#new_user input#user_password';
+const suggestionsSelector = '.suspect-email';
+const emailSuggestionSelector = '.suspect-email .email-suggestion-address';
+
+delegate('focusout', userNewEmailSelector, () => {
+ // When the user leaves the email input during account creation, we check if this account looks correct.
+ // If not (e.g if its "bidou@gmail.coo" or "john@yahoo.rf"), we attempt to suggest a fix for the invalid email.
+ const userEmailInput = document.querySelector(userNewEmailSelector);
+ const suggestedEmailSpan = document.querySelector(emailSuggestionSelector);
+
+ const suggestion = suggest(userEmailInput.value);
+ if (suggestion && suggestion.full) {
+ suggestedEmailSpan.innerHTML = suggestion.full;
+ show(document.querySelector(suggestionsSelector));
+ } else {
+ hide(document.querySelector(suggestionsSelector));
+ }
+});
+
+export function acceptEmailSuggestion() {
+ const userEmailInput = document.querySelector(userNewEmailSelector);
+ const suggestedEmailSpan = document.querySelector(emailSuggestionSelector);
+
+ userEmailInput.value = suggestedEmailSpan.innerHTML;
+ hide(document.querySelector(suggestionsSelector));
+ document.querySelector(passwordFieldSelector).focus();
+}
+
+export function discardEmailSuggestionBox() {
+ hide(document.querySelector(suggestionsSelector));
+}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 8d0eb5d3c..a0e320c3a 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -40,6 +40,10 @@ import {
} from '../new_design/state-button';
import { toggleChart } from '../new_design/toggle-chart';
import { replaceSemicolonByComma } from '../new_design/avis';
+import {
+ acceptEmailSuggestion,
+ discardEmailSuggestionBox
+} from '../new_design/user-sign_up';
// This is the global application namespace where we expose helpers used from rails views
const DS = {
@@ -50,7 +54,9 @@ const DS = {
motivationCancel,
showImportJustificatif,
toggleChart,
- replaceSemicolonByComma
+ replaceSemicolonByComma,
+ acceptEmailSuggestion,
+ discardEmailSuggestionBox
};
// Start Rails helpers
@@ -59,19 +65,14 @@ Rails.start();
Turbolinks.start();
ActiveStorage.start();
-// If Turbolinks is imported via Webpacker (and thus not available globally),
-// ReactRailsUJS will be unable to locate it.
-// https://github.com/reactjs/react-rails#event-handling
-
-// eslint-disable-next-line no-undef
-ReactRailsUJS.useContext(require.context('components', true));
-// Add Turbolinks to the global namespace:
-window.Turbolinks = Turbolinks;
-// Remove previous event handlers and add new ones:
-ReactRailsUJS.detectEvents();
-// (Optional) Clean up global namespace:
-delete window.Turbolinks;
-
// Expose globals
window.DS = window.DS || DS;
window.Chartkick = Chartkick;
+// (Both Rails redirects and ReactRailsUJS expect Turbolinks to be globally available)
+window.Turbolinks = Turbolinks;
+
+// Now that Turbolinks is globally exposed,configure ReactRailsUJS
+// eslint-disable-next-line no-undef
+ReactRailsUJS.useContext(require.context('components', true));
+// Remove previous event handlers and add new ones:
+ReactRailsUJS.detectEvents();
diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml
index 229336345..0847097fd 100644
--- a/app/views/instructeurs/procedures/show.html.haml
+++ b/app/views/instructeurs/procedures/show.html.haml
@@ -64,6 +64,7 @@
%p.explication-onglet Les dossiers de cet onglet sont archivés : vous ne pouvez plus y répondre, et les demandeurs ne peuvent plus les modifier.
- if @dossiers.present? || @current_filters.count > 0
+ = paginate @dossiers
%span.dropdown
%button.button.dropdown-button
Filtrer
diff --git a/app/views/layouts/_main_container.html.haml b/app/views/layouts/_main_container.html.haml
index a1d728b3a..ff74bc38d 100644
--- a/app/views/layouts/_main_container.html.haml
+++ b/app/views/layouts/_main_container.html.haml
@@ -1,4 +1,6 @@
#main-container{ class: "col-xs-#{main_container_size}" }
+ .row
+ = render partial: 'layouts/outdated_browser_banner'
.row
= render partial: 'layouts/flash_messages'
.row
diff --git a/app/views/layouts/_outdated_browser_banner.html.haml b/app/views/layouts/_outdated_browser_banner.html.haml
index 3e36890a6..d60b38661 100644
--- a/app/views/layouts/_outdated_browser_banner.html.haml
+++ b/app/views/layouts/_outdated_browser_banner.html.haml
@@ -1,8 +1,15 @@
-/ See config/browser.rb
-- if !browser.modern?
+-# See config/browser.rb
+- if !browser.modern? && !has_dismissed_outdated_browser_banner?
#outdated-browser-banner
- .container
- Attention, votre navigateur (#{browser.name} #{browser.version}) est trop ancien pour utiliser demarches-simplifiees.fr : certaines parties du site ne fonctionneront pas correctement. Nous vous recommandons fortement de
- %a{ href: "https://browser-update.org/fr/update.html", target: "_blank", rel: "noopener" }mettre à jour votre navigateur
- %span<>
- \.
+ .container.flex.align-center
+ .outdated-browser-icon
+ ⚠️
+ .outdated-browser-test
+ %strong
+ #{browser.name} #{browser.version} est trop ancien pour utiliser demarches-simplifiees.fr.
+ %br
+ Certaines parties du site ne fonctionneront pas correctement.
+ .outdated-browser-buttons
+ = button_to 'Ignorer', dismiss_outdated_browser_path, method: :post, remote: true, class: 'button btn', title: 'Ne plus afficher cet avertissement pendant une semaine'
+ %a.btn.button.primary{ href: "https://browser-update.org/fr/update.html", target: "_blank", rel: "noopener" }
+ Mettre à jour mon navigateur
diff --git a/app/views/layouts/application_old.html.haml b/app/views/layouts/application_old.html.haml
index ce233e5f3..80fd1109e 100644
--- a/app/views/layouts/application_old.html.haml
+++ b/app/views/layouts/application_old.html.haml
@@ -19,7 +19,6 @@
= Gon::Base.render_data(camel_case: true, init: true)
%body{ class: browser.platform.ios? ? 'ios' : nil }
- = render partial: 'layouts/outdated_browser_banner'
= render partial: 'layouts/pre_maintenance'
- if staging?
#beta
diff --git a/app/views/root/accessibilite.html.haml b/app/views/root/accessibilite.html.haml
index e790bf96a..5ed1165dd 100644
--- a/app/views/root/accessibilite.html.haml
+++ b/app/views/root/accessibilite.html.haml
@@ -14,7 +14,7 @@
%h2.new-h2 Défenseur des droits
%p.new-p
Si vous constatez un défaut d'accessibilité vous empêchant d'accéder à un contenu ou une fonctionnalité du site, que vous nous le signalez et que vous ne parvenez pas à obtenir une réponse rapide de notre part, vous êtes en droit de faire parvenir vos doléances ou une demande de saisine au Défenseur des droits. Plusieurs moyens sont à votre disposition :
- %ul
- %li un formulaire de contact ;
- %li la liste du ou des délégués de votre région avec leurs informations de contact direct ;
- %li une adresse postale : Le Défenseur des droits - 7 rue Saint-Florentin - 75409 Paris Cedex 08.
+ %ul
+ %li un formulaire de contact ;
+ %li la liste du ou des délégués de votre région avec leurs informations de contact direct ;
+ %li une adresse postale : Le Défenseur des droits - 7 rue Saint-Florentin - 75409 Paris Cedex 08.
diff --git a/app/views/shared/dossiers/editable_champs/_textarea.html.haml b/app/views/shared/dossiers/editable_champs/_textarea.html.haml
index 620f11c6d..a4666f22e 100644
--- a/app/views/shared/dossiers/editable_champs/_textarea.html.haml
+++ b/app/views/shared/dossiers/editable_champs/_textarea.html.haml
@@ -1,4 +1,4 @@
~ form.text_area :value,
- row: 6,
+ rows: 6,
required: champ.mandatory?,
value: html_to_string(champ.value)
diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml
index 60374d8e6..20c89a6e3 100644
--- a/app/views/support/index.html.haml
+++ b/app/views/support/index.html.haml
@@ -72,10 +72,10 @@
.contact-champ
= label_tag :text do
Pièce jointe
- .notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } }
- Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer.
- .notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } }
- Une capture d’écran peut nous aider à identifier plus facilement le problème.
+ %p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } }
+ Une capture d’écran peut nous aider à identifier plus facilement l’endroit à améliorer.
+ %p.notice.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AUTRE } }
+ Une capture d’écran peut nous aider à identifier plus facilement le problème.
= file_field_tag :piece_jointe
= hidden_field_tag :tags, @tags&.join(',')
diff --git a/app/views/users/dossiers/merci.html.haml b/app/views/users/dossiers/merci.html.haml
index d2da1ab4d..b3657867e 100644
--- a/app/views/users/dossiers/merci.html.haml
+++ b/app/views/users/dossiers/merci.html.haml
@@ -5,7 +5,7 @@
.merci
.container
- = image_tag('user/envoi-dossier.svg')
+ = image_tag('user/envoi-dossier.svg', alt: '')
%h1 Merci !
%p.send
Votre dossier sur la démarche
diff --git a/app/views/users/registrations/new.html.haml b/app/views/users/registrations/new.html.haml
index d21b184be..37bcc2387 100644
--- a/app/views/users/registrations/new.html.haml
+++ b/app/views/users/registrations/new.html.haml
@@ -8,6 +8,17 @@
= f.label :email, "Email"
= f.text_field :email, autofocus: true, placeholder: "Votre adresse email"
+ .suspect-email
+ .email-suggestion-title
+ Voulez-vous dire
+ %span.email-suggestion-address blabla@gmail.com
+ ?
+ .email-suggestion-answer
+ = button_tag type: 'button', class: 'button small', onclick: "DS.acceptEmailSuggestion()" do
+ Oui
+ = button_tag type: 'button', class: 'button small', onclick: "DS.discardEmailSuggestionBox()" do
+ Non
+
= f.label :password, "Mot de passe"
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum"
diff --git a/config/initializers/browser.rb b/config/initializers/browser.rb
index 0e88068ad..f9d22d40b 100644
--- a/config/initializers/browser.rb
+++ b/config/initializers/browser.rb
@@ -1,8 +1,8 @@
# See .browserslistrc
Browser.modern_rules.clear
-Browser.modern_rules << -> b { b.chrome? && b.version.to_i >= 50 }
-Browser.modern_rules << -> b { b.ie? && b.version.to_i >= 11 && !b.compatibility_view? }
+Browser.modern_rules << -> b { b.chrome? && b.version.to_i >= 50 && !b.platform.ios? }
Browser.modern_rules << -> b { b.edge? && b.version.to_i >= 14 && !b.compatibility_view? }
-Browser.modern_rules << -> b { b.firefox? && b.version.to_i >= 50 }
+Browser.modern_rules << -> b { b.firefox? && b.version.to_i >= 50 && !b.platform.ios? }
Browser.modern_rules << -> b { b.opera? && b.version.to_i >= 40 }
Browser.modern_rules << -> b { b.safari? && b.version.to_i >= 8 }
+Browser.modern_rules << -> b { b.platform.ios? && b.platform.version.to_i >= 8 }
diff --git a/config/routes.rb b/config/routes.rb
index 47bca15b9..5d4908a9e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -125,6 +125,7 @@ Rails.application.routes.draw do
get "patron" => "root#patron"
get "accessibilite" => "root#accessibilite"
get "suivi" => "root#suivi"
+ post "dismiss_outdated_browser" => "root#dismiss_outdated_browser"
get "contact", to: "support#index"
post "contact", to: "support#create"
diff --git a/package.json b/package.json
index 6a72eb949..8ccc7fce8 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"core-js": "^2.0.0",
"debounce": "^1.2.0",
"dom4": "^2.1.5",
+ "email-butler": "^1.0.12",
"highcharts": "^6.1.2",
"intersection-observer": "^0.7.0",
"jquery": "^3.4.1",
diff --git a/spec/features/outdated_browser_spec.rb b/spec/features/outdated_browser_spec.rb
new file mode 100644
index 000000000..e6cb28efb
--- /dev/null
+++ b/spec/features/outdated_browser_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+feature 'Outdated browsers support:' do
+ context 'when the user browser is outdated' do
+ before(:each) do
+ ie_11_user_agent = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko'
+ Capybara.page.driver.header('user-agent', ie_11_user_agent)
+ end
+
+ scenario 'a banner is displayed' do
+ visit new_user_session_path
+ expect(page).to have_content('Internet Explorer 11 est trop ancien')
+ end
+
+ scenario 'the banner can be dismissed' do
+ visit new_user_session_path
+ expect(page).to have_content('Internet Explorer 11 est trop ancien')
+
+ # The banner is hidden immediately
+ within '#outdated-browser-banner' do
+ click_on 'Ignorer'
+ end
+ expect(page).not_to have_content('Internet Explorer 11 est trop ancien')
+ expect(page).to have_current_path(new_user_session_path)
+
+ # The banner is hidden after a refresh
+ page.refresh
+ expect(page).not_to have_content('Internet Explorer 11 est trop ancien')
+ end
+ end
+end
diff --git a/spec/features/users/sign_up_spec.rb b/spec/features/users/sign_up_spec.rb
index 06eb1d831..ede736035 100644
--- a/spec/features/users/sign_up_spec.rb
+++ b/spec/features/users/sign_up_spec.rb
@@ -17,6 +17,39 @@ feature 'Signing up:' do
expect(page).to have_current_path commencer_path(path: procedure.path)
end
+ context 'when the user makes a typo in their email address' do
+ let(:procedure) { create :simple_procedure, :with_service }
+
+ before do
+ visit commencer_path(path: procedure.path)
+ click_on 'Créer un compte demarches-simplifiees.fr'
+ expect(page).to have_selector('.suspect-email', visible: false)
+ fill_in 'Email', with: 'bidou@yahoo.rf'
+ fill_in 'Mot de passe', with: '12345'
+ end
+
+ scenario 'they can accept the suggestion', js: true do
+ expect(page).to have_selector('.suspect-email', visible: true)
+ click_on 'Oui'
+ expect(page).to have_field("Email", :with => 'bidou@yahoo.fr')
+ expect(page).to have_selector('.suspect-email', visible: false)
+ end
+
+ scenario 'they can discard the suggestion', js: true do
+ expect(page).to have_selector('.suspect-email', visible: true)
+ click_on 'Non'
+ expect(page).to have_field("Email", :with => 'bidou@yahoo.rf')
+ expect(page).to have_selector('.suspect-email', visible: false)
+ end
+
+ scenario 'they can fix the typo themselves', js: true do
+ expect(page).to have_selector('.suspect-email', visible: true)
+ fill_in 'Email', with: 'bidou@yahoo.fr'
+ blur
+ expect(page).to have_selector('.suspect-email', visible: false)
+ end
+ end
+
scenario 'a new user can’t sign-up with too short password when visiting a procedure' do
visit commencer_path(path: procedure.path)
click_on 'Créer un compte demarches-simplifiees.fr'
diff --git a/yarn.lock b/yarn.lock
index 489582165..ff57a5c76 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3110,6 +3110,11 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
+email-butler@^1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/email-butler/-/email-butler-1.0.12.tgz#f504b45658fea6257dbc0f891be2cf822d29caa6"
+ integrity sha512-JcvRCjCpLp3fIHlP+KgdDadSmhETA/D4KUtIi3VnwEw2bJKilMOcd+xLJUHrD631bToxxxYRnxB3xTvAQeIRCQ==
+
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"