converse-register: Consolidate validation and error reporting

This commit is contained in:
JC Brand 2017-09-18 08:58:26 +02:00
parent 7cfe81ea1f
commit 4063bbfc1c
12 changed files with 131 additions and 98 deletions

View File

@ -7,6 +7,8 @@
- Remove `Login` and `Registration` tabs and consolidate into one panel. - Remove `Login` and `Registration` tabs and consolidate into one panel.
- Add validation message for an invalid JID in the login form. - Add validation message for an invalid JID in the login form.
- Don't require `auto_login` to be `true` when using the API to log in. - Don't require `auto_login` to be `true` when using the API to log in.
- Use CSS3 fade transitions to render various elements.
- Consolidate error and validation reporting on the registration form.
- #828 Add routing for the `#converse-login` and `#converse-register` URL - #828 Add routing for the `#converse-login` and `#converse-register` URL
fragments, which will render the registration and login forms respectively. fragments, which will render the registration and login forms respectively.

View File

@ -1281,6 +1281,9 @@
#converse-embedded-chat .error, #converse-embedded-chat .error,
#conversejs .error { #conversejs .error {
color: #A53214; } color: #A53214; }
#converse-embedded-chat .info,
#conversejs .info {
color: #1E9652; }
#converse-embedded-chat .reg-feedback, #converse-embedded-chat .reg-feedback,
#conversejs .reg-feedback { #conversejs .reg-feedback {
font-size: 85%; font-size: 85%;
@ -1945,8 +1948,8 @@
font-size: 90%; font-size: 90%;
margin: 1.5em 0; } margin: 1.5em 0; }
#conversejs #controlbox #converse-register .form-errors { #conversejs #controlbox #converse-register .form-errors {
color: red; color: #A53214;
display: none; } margin: 1em 0; }
#conversejs #controlbox #converse-register .provider-title { #conversejs #controlbox #converse-register .provider-title {
font-size: 20px; font-size: 20px;
margin: 0; } margin: 0; }

View File

@ -1281,6 +1281,9 @@
#converse-embedded-chat .error, #converse-embedded-chat .error,
#conversejs .error { #conversejs .error {
color: #A53214; } color: #A53214; }
#converse-embedded-chat .info,
#conversejs .info {
color: #1E9652; }
#converse-embedded-chat .reg-feedback, #converse-embedded-chat .reg-feedback,
#conversejs .reg-feedback { #conversejs .reg-feedback {
font-size: 85%; font-size: 85%;
@ -2033,8 +2036,8 @@ body {
font-size: 90%; font-size: 90%;
margin: 1.5em 0; } margin: 1.5em 0; }
#conversejs #controlbox #converse-register .form-errors { #conversejs #controlbox #converse-register .form-errors {
color: red; color: #A53214;
display: none; } margin: 1em 0; }
#conversejs #controlbox #converse-register .provider-title { #conversejs #controlbox #converse-register .provider-title {
font-size: 26px; font-size: 26px;
margin: 0; } margin: 0; }
@ -2401,6 +2404,11 @@ body {
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
#conversejs #controlbox .brand-heading-container .brand-heading { #conversejs #controlbox .brand-heading-container .brand-heading {
font-size: 400%; } } font-size: 400%; } }
#conversejs #controlbox .controlbox-panes {
background-color: white; }
#conversejs #controlbox .controlbox-pane {
height: -webkit-calc(100% - 63px);
height: calc(100% - 63px); }
#conversejs #controlbox.logged-out { #conversejs #controlbox.logged-out {
opacity: 0; opacity: 0;
/* make things invisible upon start */ /* make things invisible upon start */
@ -2422,6 +2430,8 @@ body {
#conversejs #controlbox.logged-out .box-flyout .controlbox-head { #conversejs #controlbox.logged-out .box-flyout .controlbox-head {
background-color: white; background-color: white;
height: 0; } height: 0; }
#conversejs #controlbox.logged-out .box-flyout .controlbox-pane {
height: auto; }
#conversejs #controlbox .box-flyout { #conversejs #controlbox .box-flyout {
border: 0; border: 0;
min-width: 250px; min-width: 250px;
@ -2459,10 +2469,6 @@ body {
font-size: 18px; } font-size: 18px; }
#conversejs #controlbox #controlbox-tabs li a.current, #conversejs #controlbox #controlbox-tabs li a.current:hover { #conversejs #controlbox #controlbox-tabs li a.current, #conversejs #controlbox #controlbox-tabs li a.current:hover {
height: 63px; } height: 63px; }
#conversejs #controlbox .controlbox-panes {
background-color: white; }
#conversejs #controlbox .controlbox-pane {
height: auto; }
#conversejs #converse-roster { #conversejs #converse-roster {
text-align: left; text-align: left;

View File

@ -73,8 +73,8 @@
margin: 1.5em 0; margin: 1.5em 0;
} }
.form-errors { .form-errors {
color: red; color: $error-color;
display: none; margin: 1em 0;
} }
.provider-title { .provider-title {
font-size: $font-size-huge; font-size: $font-size-huge;

View File

@ -152,6 +152,9 @@
.error { .error {
color: $error-color; color: $error-color;
} }
.info {
color: $info-color;
}
.reg-feedback { .reg-feedback {
font-size: 85%; font-size: 85%;
margin-bottom: 1em; margin-bottom: 1em;

View File

@ -81,6 +81,7 @@ $light-background-color: #FCFDFD !default;
$moderator-color: $dark-red !default; $moderator-color: $dark-red !default;
$online-color: $green !default; $online-color: $green !default;
$error-color: $darkest-red !default; $error-color: $darkest-red !default;
$info-color: $dark-green !default;
$button-border-radius: 5px !default; $button-border-radius: 5px !default;
$chatbox-border-radius: 4px !default; $chatbox-border-radius: 4px !default;

View File

@ -32,6 +32,12 @@
} }
} }
.controlbox-panes {
background-color: white;
}
.controlbox-pane {
@include calc(height, '100% - #{$controlbox-head-height}');
}
&.logged-out { &.logged-out {
@include fade-in; @include fade-in;
width: 100%; width: 100%;
@ -41,6 +47,9 @@
background-color: white; background-color: white;
height: 0; height: 0;
} }
.controlbox-pane {
height: auto;
}
} }
} }
.box-flyout { .box-flyout {
@ -93,11 +102,5 @@
} }
} }
} }
.controlbox-panes {
background-color: white;
}
.controlbox-pane {
height: auto;
}
} }
} }

View File

@ -85,6 +85,7 @@ $light-background-color: #FCFDFD !default;
$moderator-color: $red !default; $moderator-color: $red !default;
$online-color: $green !default; $online-color: $green !default;
$error-color: $darkest-red !default; $error-color: $darkest-red !default;
$info-color: $dark-green !default;
$button-border-radius: 5px !default; $button-border-radius: 5px !default;
$chatbox-border-radius: 0 !default; $chatbox-border-radius: 0 !default;

View File

@ -100,7 +100,7 @@
_converse.OPENED = 'opened'; _converse.OPENED = 'opened';
_converse.PREBIND = "prebind"; _converse.PREBIND = "prebind";
const PRETTY_CONNECTION_STATUS = { _converse.PRETTY_CONNECTION_STATUS = {
0: 'ERROR', 0: 'ERROR',
1: 'CONNECTING', 1: 'CONNECTING',
2: 'CONNFAIL', 2: 'CONNFAIL',
@ -495,7 +495,7 @@
* through various states while establishing or tearing down a * through various states while establishing or tearing down a
* connection. * connection.
*/ */
_converse.log(`Status changed to: ${PRETTY_CONNECTION_STATUS[status]}`); _converse.log(`Status changed to: ${_converse.PRETTY_CONNECTION_STATUS[status]}`);
if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) { if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {
_converse.giveFeedback(); _converse.giveFeedback();
// By default we always want to send out an initial presence stanza. // By default we always want to send out an initial presence stanza.

View File

@ -145,6 +145,11 @@
const { _converse } = this, const { _converse } = this,
{ __ } = _converse; { __ } = _converse;
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.REGIFAIL] = 'REGIFAIL';
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.REGISTERED] = 'REGISTERED';
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.CONFLICT] = 'CONFLICT';
_converse.PRETTY_CONNECTION_STATUS[Strophe.Status.NOTACCEPTABLE] = 'NOTACCEPTABLE';
_converse.api.settings.update({ _converse.api.settings.update({
allow_registration: true, allow_registration: true,
domain_placeholder: __(" e.g. conversejs.org"), // Placeholder text shown in the domain input on the registration form domain_placeholder: __(" e.g. conversejs.org"), // Placeholder text shown in the domain input on the registration form
@ -174,7 +179,7 @@
className: 'controlbox-pane fade-in', className: 'controlbox-pane fade-in',
events: { events: {
'submit form#converse-register': 'onProviderChosen', 'submit form#converse-register': 'onProviderChosen',
'click .button-cancel': 'cancelRegistration', 'click .button-cancel': 'renderProviderChoiceForm',
}, },
initialize (cfg) { initialize (cfg) {
@ -231,6 +236,9 @@
const body = conn._proto._reqToData(req); const body = conn._proto._reqToData(req);
if (!body) { return; } if (!body) { return; }
if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) { if (conn._proto._connect_cb(body) === Strophe.Status.CONNFAIL) {
this.showValidationError(
__("Sorry, we're unable to connect to your chosen provider.")
);
return false; return false;
} }
const register = body.getElementsByTagName("register"); const register = body.getElementsByTagName("register");
@ -240,12 +248,11 @@
return false; return false;
} }
if (register.length === 0) { if (register.length === 0) {
conn._changeConnectStatus( conn._changeConnectStatus(Strophe.Status.REGIFAIL);
Strophe.Status.REGIFAIL, this.showValidationError(
__("Sorry, the given provider does not support in "+ __("Sorry, the given provider does not support in "+
"band account registration. Please try with a "+ "band account registration. Please try with a "+
"different provider.") "different provider."))
);
return true; return true;
} }
// Send an IQ stanza to get all required data fields // Send an IQ stanza to get all required data fields
@ -266,12 +273,16 @@
if (stanza.getAttribute("type") === "error") { if (stanza.getAttribute("type") === "error") {
_converse.connection._changeConnectStatus( _converse.connection._changeConnectStatus(
Strophe.Status.REGIFAIL, Strophe.Status.REGIFAIL,
__('Something went wrong while establishing a connection with "%1$s". Are you sure it exists?', this.domain) __('Something went wrong while establishing a connection with "%1$s".'+
'Are you sure it exists?', this.domain)
); );
return false; return false;
} }
if (stanza.getElementsByTagName("query").length !== 1) { if (stanza.getElementsByTagName("query").length !== 1) {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown"); _converse.connection._changeConnectStatus(
Strophe.Status.REGIFAIL,
"unknown"
);
return false; return false;
} }
this.setFields(stanza); this.setFields(stanza);
@ -331,7 +342,9 @@
domain: Strophe.getDomainFromJid(domain_name), domain: Strophe.getDomainFromJid(domain_name),
_registering: true _registering: true
}); });
_converse.connection.connect(this.domain, "", this.onRegistering.bind(this)); _converse.connection.connect(
this.domain, "", this.onConnectStatusChanged.bind(this)
);
return false; return false;
}, },
@ -350,18 +363,18 @@
giveFeedback (message, klass) { giveFeedback (message, klass) {
let feedback = this.el.querySelector('.reg-feedback'); let feedback = this.el.querySelector('.reg-feedback');
if (_.isNull(feedback)) { if (!_.isNull(feedback)) {
feedback.parentNode.removeChild(feedback);
}
const form = this.el.querySelector('form'); const form = this.el.querySelector('form');
form.insertAdjacentHTML( form.insertAdjacentHTML(
'afterbegin', 'afterbegin',
'<span class="reg-feedback"></span>' '<span class="reg-feedback"></span>'
); );
feedback = form.querySelector('.reg-feedback'); feedback = form.querySelector('.reg-feedback');
}
feedback.setAttribute('class', 'reg-feedback');
feedback.textContent = message; feedback.textContent = message;
if (klass) { if (klass) {
$('.reg-feedback').addClass(klass); feedback.classList.add(klass);
} }
}, },
@ -377,34 +390,31 @@
return form; return form;
}, },
onRegistering (status, error) { onConnectStatusChanged(status_code) {
/* Callback function called by Strophe */ /* Callback function called by Strophe whenever the
_converse.log('onRegistering'); * connection status changes.
*
* Passed to Strophe specifically during a registration
* attempt.
*
* Parameters:
* (Integer) status_code - The Stroph.Status status code
*/
_converse.log('converse-register: onConnectStatusChanged');
if (_.includes([ if (_.includes([
Strophe.Status.DISCONNECTED, Strophe.Status.DISCONNECTED,
Strophe.Status.CONNFAIL, Strophe.Status.CONNFAIL,
Strophe.Status.REGIFAIL, Strophe.Status.REGIFAIL,
Strophe.Status.NOTACCEPTABLE, Strophe.Status.NOTACCEPTABLE,
Strophe.Status.CONFLICT Strophe.Status.CONFLICT
], status)) { ], status_code)) {
_converse.log( _converse.log(
`Problem during registration: Strophe.Status is: ${status}`, `Problem during registration: Strophe.Status is ${_converse.PRETTY_CONNECTION_STATUS[status_code]}`,
Strophe.LogLevel.ERROR Strophe.LogLevel.ERROR
); );
this.cancelRegistration(error); this.abortRegistration();
if (error) { } else if (status_code === Strophe.Status.REGISTERED) {
this.giveFeedback(__(
'Something went wrong while establishing a connection with "%1$s". The returned error message is "%2$s"',
this.domain, error
), 'error');
} else {
this.giveFeedback(__(
'Something went wrong while establishing a connection with "%1$s". Are you sure it exists?',
this.domain
), 'error');
}
} else if (status === Strophe.Status.REGISTERED) {
router.navigate(); // Strip the URL fragment router.navigate(); // Strip the URL fragment
_converse.log("Registered successfully."); _converse.log("Registered successfully.");
this.model.set('registration_form_rendered', false); this.model.set('registration_form_rendered', false);
@ -419,7 +429,7 @@
this.fields.password, this.fields.password,
_converse.onConnectStatusChanged _converse.onConnectStatusChanged
); );
this.giveFeedback(__('Now logging you in')); this.giveFeedback(__('Now logging you in'), 'info');
} else { } else {
_converse.chatboxviews.get('controlbox').renderLoginPanel(); _converse.chatboxviews.get('controlbox').renderLoginPanel();
_converse.giveFeedback(__('Registered successfully')); _converse.giveFeedback(__('Registered successfully'));
@ -504,12 +514,34 @@
`<input type="button" class="submit" value="${__('Return')}"/>` `<input type="button" class="submit" value="${__('Return')}"/>`
); );
form.querySelector('input[type=button]').addEventListener( form.querySelector('input[type=button]').addEventListener(
'click', this.cancelRegistration.bind(this)); 'click', this.renderProviderChoiceForm.bind(this));
} }
this.model.set('registration_form_rendered', true); this.model.set('registration_form_rendered', true);
this.showRegistrationForm(); this.showRegistrationForm();
}, },
showValidationError (message) {
const form = this.el.querySelector('form');
let flash = form.querySelector('.form-errors');
if (_.isNull(flash)) {
flash = '<div class="form-errors hidden"></div>';
const instructions = form.querySelector('p.instructions');
if (_.isNull(instructions)) {
form.insertAdjacentHTML('afterbegin', flash);
} else {
instructions.insertAdjacentHTML('afterend', flash);
}
flash = form.querySelector('.form-errors');
} else {
flash.innerHTML = '';
}
flash.insertAdjacentHTML(
'beforeend',
'<p class="form-help error">'+message+'</p>'
);
flash.classList.remove('hidden');
},
reportErrors (stanza) { reportErrors (stanza) {
/* Report back to the user any error messages received from the /* Report back to the user any error messages received from the
* XMPP server after attempted registration. * XMPP server after attempted registration.
@ -518,42 +550,33 @@
* (XMLElement) stanza - The IQ stanza received from the * (XMLElement) stanza - The IQ stanza received from the
* XMPP server. * XMPP server.
*/ */
const $form= this.$('form'), const errors = stanza.querySelectorAll('error');
$errmsgs = $(stanza).find('error text'); _.each(errors, (error) => {
this.showValidationError(error.textContent);
let $flash = $form.find('.form-errors');
if (!$flash.length) {
const flash = '<legend class="form-errors"></legend>';
if ($form.find('p.instructions').length) {
$form.find('p.instructions').append(flash);
} else {
$form.prepend(flash);
}
$flash = $form.find('.form-errors');
} else {
$flash.empty();
}
$errmsgs.each(function (idx, txt) {
$flash.append($('<p class="form-help error">').text($(txt).text()));
}); });
if (!$errmsgs.length) { if (!errors.length) {
$flash.append($('<p class="form-help error">').text( const message = __('The provider rejected your registration attempt. '+
__('The provider rejected your registration attempt. '+ 'Please check the values you entered for correctness.');
'Please check the values you entered for correctness.'))); this.showValidationError(message);
} }
$flash.show();
}, },
cancelRegistration (ev) { renderProviderChoiceForm (ev) {
/* Handler, when the user cancels the registration form.
*/
if (ev && ev.preventDefault) { ev.preventDefault(); } if (ev && ev.preventDefault) { ev.preventDefault(); }
_converse.connection._proto._abortAllRequests(); _converse.connection._proto._abortAllRequests();
_converse.connection.reset(); _converse.connection.reset();
this.render();
},
abortRegistration () {
_converse.connection._proto._abortAllRequests();
_converse.connection.reset();
if (this.model.get('registration_form_rendered')) {
if (_converse.registration_domain && this.model.get('registration_form_rendered')) { if (_converse.registration_domain && this.model.get('registration_form_rendered')) {
this.fetchRegistrationForm( this.fetchRegistrationForm(
_converse.registration_domain _converse.registration_domain
); );
}
} else { } else {
this.render(); this.render();
} }
@ -657,14 +680,11 @@
* Parameters: * Parameters:
* (XMLElement) stanza - The IQ stanza. * (XMLElement) stanza - The IQ stanza.
*/ */
let error = null,
query = stanza.getElementsByTagName("query");
if (query.length > 0) {
query = query[0];
}
if (stanza.getAttribute("type") === "error") { if (stanza.getAttribute("type") === "error") {
_converse.log("Registration failed.", Strophe.LogLevel.ERROR); _converse.log("Registration failed.", Strophe.LogLevel.ERROR);
error = stanza.getElementsByTagName("error"); this.reportErrors(stanza);
let error = stanza.getElementsByTagName("error");
if (error.length !== 1) { if (error.length !== 1) {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown"); _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, "unknown");
return false; return false;
@ -677,16 +697,10 @@
} else { } else {
_converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error); _converse.connection._changeConnectStatus(Strophe.Status.REGIFAIL, error);
} }
this.reportErrors(stanza);
} else { } else {
_converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null); _converse.connection._changeConnectStatus(Strophe.Status.REGISTERED, null);
} }
return false; return false;
},
remove () {
this.$tabs.empty();
this.$el.parent().empty();
} }
}); });
} }

View File

@ -1,9 +1,9 @@
<form id="converse-register" class="pure-form converse-form"> <form id="converse-register" class="pure-form converse-form">
<legend>{{{__("Account Registration")}}}</legend> <legend>{{{__("Account Registration")}}}</legend>
<span class="reg-feedback"></span>
<label>{{{__("Please enter the XMPP provider to register with:")}}}</label> <label>{{{__("Please enter the XMPP provider to register with:")}}}</label>
<p class="form-help">{{{help_providers}}} <a href="{{{href_providers}}}" class="url" target="_blank" rel="noopener">{{{help_providers_link}}}</a>.</p> <p class="form-help">{{{help_providers}}} <a href="{{{href_providers}}}" class="url" target="_blank" rel="noopener">{{{help_providers_link}}}</a>.</p>
<div class="form-errors hidden"></div>
{[ if (default_domain) { ]} {[ if (default_domain) { ]}
{{{default_domain}}} {{{default_domain}}}

View File

@ -1,4 +1,4 @@
<legend>{{{__("Account Registration:")}}} {{{domain}}}</legend> <legend>{{{__("Account Registration:")}}} {{{domain}}}</legend>
<span class="reg-feedback"></span>
<p class="title">{{{title}}}</p> <p class="title">{{{title}}}</p>
<p class="instructions">{{{instructions}}}</p> <p class="instructions">{{{instructions}}}</p>
<div class="form-errors hidden"></div>