Skip to content

Commit 4b2db6c

Browse files
authored
✨Improve phone registration (ITISFoundation#3624)
1 parent 0323aaa commit 4b2db6c

File tree

10 files changed

+1675
-11
lines changed

10 files changed

+1675
-11
lines changed

services/static-webserver/client/source/class/osparc/Application.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ qx.Class.define("osparc.Application", {
6666
qx.log.appender.Native;
6767
}
6868

69+
const intlTelInput = osparc.wrapper.IntlTelInput.getInstance();
70+
intlTelInput.init();
71+
6972
const webSocket = osparc.wrapper.WebSocket.getInstance();
7073
webSocket.addListener("connect", () => osparc.io.WatchDog.getInstance().setOnline(true));
7174
webSocket.addListener("disconnect", () => osparc.io.WatchDog.getInstance().setOnline(false));

services/static-webserver/client/source/class/osparc/auth/ui/VerifyPhoneNumberView.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ qx.Class.define("osparc.auth.ui.VerifyPhoneNumberView", {
4545
},
4646

4747
members: {
48-
__phoneNumberTF: null,
48+
__itiInput: null,
4949
__verifyPhoneNumberBtn: null,
5050
__validateCodeTF: null,
5151
__validateCodeBtn: null,
@@ -71,22 +71,27 @@ qx.Class.define("osparc.auth.ui.VerifyPhoneNumberView", {
7171
this.add(verificationInfoDesc);
7272

7373
const phoneNumberVerifyLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
74-
const phoneNumber = this.__phoneNumberTF = new qx.ui.form.TextField().set({
75-
required: true,
76-
placeholder: this.tr("Type your phone number")
74+
phoneNumberVerifyLayout.getContentElement().setStyles({
75+
"overflow": "visible" // needed for countries dropdown menu
7776
});
78-
phoneNumberVerifyLayout.add(phoneNumber, {
77+
78+
const itiInput = this.__itiInput = new osparc.component.widget.IntlTelInput();
79+
phoneNumberVerifyLayout.add(itiInput, {
7980
flex: 1
8081
});
82+
8183
const verifyPhoneNumberBtn = this.__verifyPhoneNumberBtn = new qx.ui.form.Button(this.tr("Send SMS")).set({
84+
maxHeight: 23,
8285
minWidth: 80
8386
});
8487
phoneNumberVerifyLayout.add(verifyPhoneNumberBtn);
8588
this.add(phoneNumberVerifyLayout);
8689
},
8790

8891
__buildValidationLayout: function() {
89-
const smsValidationLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
92+
const smsValidationLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)).set({
93+
zIndex: 1 // the contries list that goes on top has a z-index of 2
94+
});
9095
const validationCode = this.__validateCodeTF = new qx.ui.form.TextField().set({
9196
placeholder: this.tr("Type the SMS code"),
9297
enabled: false
@@ -108,20 +113,21 @@ qx.Class.define("osparc.auth.ui.VerifyPhoneNumberView", {
108113
},
109114

110115
__verifyPhoneNumber: function() {
111-
const isValid = osparc.auth.core.Utils.phoneNumberValidator(this.__phoneNumberTF.getValue(), this.__phoneNumberTF);
116+
this.__itiInput.verifyPhoneNumber();
117+
const isValid = this.__itiInput.isValidNumber();
112118
if (isValid) {
113-
this.__phoneNumberTF.setEnabled(false);
119+
this.__itiInput.setEnabled(false);
114120
this.__verifyPhoneNumberBtn.setEnabled(false);
115121
this.self().restartResendTimer(this.__verifyPhoneNumberBtn, this.tr("Send SMS"));
116-
osparc.auth.Manager.getInstance().verifyPhoneNumber(this.getUserEmail(), this.__phoneNumberTF.getValue())
122+
osparc.auth.Manager.getInstance().verifyPhoneNumber(this.getUserEmail(), this.__itiInput.getNumber())
117123
.then(data => {
118124
osparc.component.message.FlashMessenger.logAs(data.message, "INFO");
119125
this.__validateCodeTF.setEnabled(true);
120126
this.__validateCodeBtn.setEnabled(true);
121127
})
122128
.catch(err => {
123129
osparc.component.message.FlashMessenger.logAs(err.message, "ERROR");
124-
this.__phoneNumberTF.setEnabled(true);
130+
this.__itiInput.setEnabled(true);
125131
});
126132
}
127133
},
@@ -150,7 +156,7 @@ qx.Class.define("osparc.auth.ui.VerifyPhoneNumberView", {
150156
};
151157

152158
const manager = osparc.auth.Manager.getInstance();
153-
manager.validateCodeRegister(this.getUserEmail(), this.__phoneNumberTF.getValue(), this.__validateCodeTF.getValue(), loginFun, failFun, this);
159+
manager.validateCodeRegister(this.getUserEmail(), this.__itiInput.getNumber(), this.__validateCodeTF.getValue(), loginFun, failFun, this);
154160
}
155161
}
156162
});
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2022 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
/* global intlTelInput */
19+
20+
/**
21+
* @ignore(intlTelInput)
22+
*/
23+
24+
qx.Class.define("osparc.component.widget.IntlTelInput", {
25+
extend: qx.ui.core.Widget,
26+
27+
construct: function() {
28+
this.base(arguments);
29+
30+
this._setLayout(new qx.ui.layout.HBox(5));
31+
32+
this.getContentElement().setStyles({
33+
"overflow": "visible" // needed for countries dropdown menu
34+
});
35+
36+
const randId = Math.floor(Math.random() * 100);
37+
const html = `<input type='tel' id='phone-${randId}' name='phone' autocomplete='off'>`;
38+
const phoneNumber = new qx.ui.embed.Html(html).set({
39+
minWidth: 185,
40+
maxHeight: 25
41+
});
42+
this._add(phoneNumber);
43+
phoneNumber.addListenerOnce("appear", () => {
44+
const convertInputToPhoneInput = () => {
45+
const domElement = document.querySelector(`#phone-${randId}`);
46+
this.__itiInput = this.__inputToPhoneInput(domElement);
47+
phoneNumber.getContentElement().setStyles({
48+
"overflow": "visible" // needed for countries dropdown menu
49+
});
50+
};
51+
const intlTelInputLib = osparc.wrapper.IntlTelInput.getInstance();
52+
if (intlTelInputLib.getLibReady()) {
53+
convertInputToPhoneInput();
54+
} else {
55+
intlTelInputLib.addListenerOnce("changeLibReady", e => {
56+
if (e.getData()) {
57+
convertInputToPhoneInput();
58+
}
59+
});
60+
}
61+
});
62+
63+
const feedbackCheck = this.__feedbackCheck = new qx.ui.basic.Image().set({
64+
paddingTop: 3
65+
});
66+
feedbackCheck.exclude();
67+
this._add(feedbackCheck);
68+
},
69+
70+
statics: {
71+
updateStyle: function(itiInput, checkIcon) {
72+
itiInput.a.style["width"] = checkIcon && checkIcon.isVisible() ? "185px" : "215px";
73+
itiInput.a.style["height"] = "23px";
74+
itiInput.a.style["borderWidth"] = "0px";
75+
itiInput.a.style["backgroundColor"] = qx.theme.manager.Meta.getInstance().getTheme().name.includes("Light") ? "#eaedef" : "#202426";
76+
itiInput.a.style["color"] = qx.theme.manager.Color.getInstance().resolve("text");
77+
}
78+
},
79+
80+
members: {
81+
__itiInput: null,
82+
__feedbackCheck: null,
83+
84+
getNumber: function() {
85+
return this.__itiInput.getNumber();
86+
},
87+
88+
isValidNumber: function() {
89+
return this.__itiInput.isValidNumber();
90+
},
91+
92+
verifyPhoneNumber: function() {
93+
const isValid = this.isValidNumber();
94+
this.__feedbackCheck.set({
95+
toolTipText: "E.164: " + this.getNumber(),
96+
source: isValid ? "@FontAwesome5Solid/check/18" : "@FontAwesome5Solid/exclamation-triangle/18",
97+
textColor: isValid ? "text" : "failed-red"
98+
});
99+
this.__feedbackCheck.show();
100+
if (!isValid) {
101+
const validationError = this.__itiInput.getValidationError();
102+
const errorMap = {
103+
0: this.tr("Invalid number"),
104+
1: this.tr("Invalid country code"),
105+
2: this.tr("Number too short"),
106+
3: this.tr("Number too long")
107+
};
108+
const errorMsg = validationError in errorMap ? errorMap[validationError] : "Invalid number";
109+
this.__feedbackCheck.set({
110+
toolTipText: errorMsg + ". " + this.__feedbackCheck.getToolTipText()
111+
});
112+
}
113+
this.self().updateStyle(this.__itiInput, this.__feedbackCheck);
114+
},
115+
116+
__inputToPhoneInput: function(input) {
117+
const iti = intlTelInput(input, {
118+
initialCountry: "ch", // auto: geoIpLookup. need to unlock https://ipinfo.io/,
119+
preferredCountries: ["ch", "us"]
120+
});
121+
const themeManager = qx.theme.manager.Meta.getInstance();
122+
themeManager.addListener("changeTheme", () => this.self().updateStyle(iti));
123+
this.self().updateStyle(iti);
124+
return iti;
125+
}
126+
}
127+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2022 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
/**
19+
* @asset(intl-tel-input/js/intlTelInput.min.js)
20+
* @asset(intl-tel-input/js/data.min.js)
21+
* @asset(intl-tel-input/js/utils.js)
22+
* @asset(intl-tel-input/css/intlTelInput.css)
23+
* @asset(intl-tel-input/img/flags.png)
24+
* @asset(intl-tel-input/img/[email protected])
25+
*/
26+
27+
/**
28+
* A qooxdoo wrapper for
29+
* <a href='https://github.com/jackocnr/intl-tel-input' target='_blank'>IntlTelInput</a>
30+
*/
31+
32+
qx.Class.define("osparc.wrapper.IntlTelInput", {
33+
extend: qx.core.Object,
34+
type: "singleton",
35+
36+
statics: {
37+
NAME: "intlTelInput",
38+
VERSION: "17.0.19",
39+
URL: "https://github.com/jackocnr/intl-tel-input"
40+
},
41+
42+
properties: {
43+
libReady: {
44+
check: "Boolean",
45+
init: false,
46+
nullable: false,
47+
event: "changeLibReady"
48+
}
49+
},
50+
members: {
51+
init: function() {
52+
// initialize the script loading
53+
let intlTelInputPath = "intl-tel-input/js/intlTelInput.min.js";
54+
let dataPath = "intl-tel-input/js/data.min.js";
55+
let utilsPath = "intl-tel-input/js/utils.js";
56+
let intlTelInputCss = "intl-tel-input/css/intlTelInput.css";
57+
let intlTelInputCssUri = qx.util.ResourceManager.getInstance().toUri(intlTelInputCss);
58+
qx.module.Css.includeStylesheet(intlTelInputCssUri);
59+
let dynLoader = new qx.util.DynamicScriptLoader([
60+
intlTelInputPath,
61+
dataPath,
62+
utilsPath
63+
]);
64+
65+
dynLoader.addListenerOnce("ready", () => {
66+
console.log(intlTelInputPath + " loaded");
67+
this.setLibReady(true);
68+
}, this);
69+
70+
dynLoader.addListener("failed", e => {
71+
let data = e.getData();
72+
console.error("failed to load " + data.script);
73+
}, this);
74+
75+
dynLoader.start();
76+
}
77+
}
78+
});

0 commit comments

Comments
 (0)