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.
- 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.
- 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
fragments, which will render the registration and login forms respectively.

View File

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

View File

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

View File

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

View File

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

View File

@ -81,6 +81,7 @@ $light-background-color: #FCFDFD !default;
$moderator-color: $dark-red !default;
$online-color: $green !default;
$error-color: $darkest-red !default;
$info-color: $dark-green !default;
$button-border-radius: 5px !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 {
@include fade-in;
width: 100%;
@ -41,6 +47,9 @@
background-color: white;
height: 0;
}
.controlbox-pane {
height: auto;
}
}
}
.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;
$online-color: $green !default;
$error-color: $darkest-red !default;
$info-color: $dark-green !default;
$button-border-radius: 5px !default;
$chatbox-border-radius: 0 !default;

View File

@ -100,7 +100,7 @@
_converse.OPENED = 'opened';
_converse.PREBIND = "prebind";
const PRETTY_CONNECTION_STATUS = {
_converse.PRETTY_CONNECTION_STATUS = {
0: 'ERROR',
1: 'CONNECTING',
2: 'CONNFAIL',
@ -495,7 +495,7 @@
* through various states while establishing or tearing down a
* 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) {
_converse.giveFeedback();
// By default we always want to send out an initial presence stanza.

View File

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

View File

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