Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
128
gaia/apps/system/mobile_id/index.html
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<link href="../shared/style/progress_activity.css" rel="stylesheet" type="text/css">
|
||||
<link href="../shared/style/value_selector.css" rel="stylesheet" type="text/css">
|
||||
<link href="../shared/style/buttons.css" rel="stylesheet" type="text/css">
|
||||
<link href="../shared/style/input_areas.css" rel="stylesheet" type="text/css">
|
||||
<link href="../shared/style/action_menu.css" rel="stylesheet" type="text/css">
|
||||
<link href="../shared/style/confirm.css" rel="stylesheet" type="text/css">
|
||||
<link href="style/error_overlay.css" rel="stylesheet" type="text/css">
|
||||
<link href="style/mobile_id.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/mobile_id.{locale}.properties">
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<link href="app://theme.gaiamobile.org/shared/elements/gaia-theme/gaia-theme.css" rel="stylesheet" type="text/css">
|
||||
<link href="../shared/elements/gaia-icons/gaia-icons.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<link rel="resource" type="application/json" href="shared/resources/mcc.json"/>
|
||||
<script defer src="../shared/js/l10n.js"></script>
|
||||
<script defer src="../shared/js/utilities.js"></script>
|
||||
|
||||
<!-- Gaia Components -->
|
||||
<script src="../shared/elements/config.js"></script>
|
||||
<script src="../shared/elements/gaia-header/dist/gaia-header.js"></script>
|
||||
|
||||
<script defer src="js/error_overlay.js"></script>
|
||||
<script defer src="js/ui.js"></script>
|
||||
<script defer src="js/controller.js"></script>
|
||||
<script defer src="js/mobile_id.js"></script>
|
||||
|
||||
<title>Mobile ID</title>
|
||||
</head>
|
||||
<body class="theme-settings" role="application">
|
||||
<section class="skin-organic" role="region">
|
||||
<gaia-header id="header" action="close">
|
||||
<h1 data-l10n-id="errorTitle"></h1>
|
||||
</gaia-header>
|
||||
<progress id="verification-code-timer" value="" max="100"></progress>
|
||||
<!--
|
||||
We will contain the three main panels of the Mobile ID flow:
|
||||
- Selection of the phone number
|
||||
- Add verification code (if required)
|
||||
- Feedback screen
|
||||
-->
|
||||
<section id="panels-container" class="panels-container">
|
||||
<div class="panel msisdn-selection-panel">
|
||||
<p id="mobile-id-explanation" class="explanation">
|
||||
</p>
|
||||
<div class="msisdn-overflow-shield">
|
||||
<section class="msisdn-selection-wrapper">
|
||||
<div>
|
||||
<ul class="phone-options-list">
|
||||
</ul>
|
||||
<div id="add-msisdn" data-l10n-id="addPhoneNumber">
|
||||
</div>
|
||||
</div>
|
||||
<div class="manual-msisdn">
|
||||
<ul class="msisdn-form">
|
||||
<li>
|
||||
<span id="legend-parent" class="button icon icon-dialog button-select-wrapper">
|
||||
<span id="legend"></span>
|
||||
<select id="country-codes-select" hidden>
|
||||
</select>
|
||||
</span>
|
||||
</li>
|
||||
<li><input type="tel" id="msisdn-input"></li>
|
||||
</ul>
|
||||
<div id="do-automatic-msisdn" data-l10n-id="selectPhoneNumber">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel verification-panel">
|
||||
<p class="explanation" id="verification-code-explanation">
|
||||
</p>
|
||||
<input type="number" id="verification-code">
|
||||
</div>
|
||||
<div class="panel done-panel">
|
||||
<p class="explanation" id="success-explanation">
|
||||
Great! Know your phone number is registered.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<div id="msb" class="multistate-button">
|
||||
<button id="allow-button" class="msb-button-step state-allow">
|
||||
<div class="states-wrapper">
|
||||
<div data-l10n-id="share">
|
||||
</div>
|
||||
<div>
|
||||
<progress></progress><span data-l10n-id="connecting"></span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button id="verify-button" class="msb-button-step state-verify">
|
||||
<div class="states-wrapper">
|
||||
<div>
|
||||
<span></span>
|
||||
<span data-l10n-id="resend"></span>
|
||||
</div>
|
||||
<div data-l10n-id="verify">
|
||||
</div>
|
||||
<div>
|
||||
<progress></progress><span data-l10n-id="verifying"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span data-l10n-id="verified"></span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Error message overlay -->
|
||||
<form role="dialog" data-type="confirm" id="mobileid-error-overlay">
|
||||
<section>
|
||||
<h1 id="mobileid-error-title"></h1>
|
||||
<p><small id="mobileid-error-msg"></small></p>
|
||||
<menu>
|
||||
<button class="full" data-l10n-id="ok" id="mobileid-error-ok">OK</button>
|
||||
</menu>
|
||||
</section>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
80
gaia/apps/system/mobile_id/js/controller.js
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
/* exports Controller */
|
||||
/* global UI */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
function _localize(appName) {
|
||||
navigator.mozL10n.ready(function localized() {
|
||||
UI.localize(appName);
|
||||
});
|
||||
}
|
||||
|
||||
var Controller = {
|
||||
init: function c_init() {
|
||||
window.addEventListener(
|
||||
'init',
|
||||
this
|
||||
);
|
||||
window.addEventListener(
|
||||
'shown',
|
||||
this
|
||||
);
|
||||
window.addEventListener(
|
||||
'onverifying',
|
||||
this
|
||||
);
|
||||
window.addEventListener(
|
||||
'onverified',
|
||||
this
|
||||
);
|
||||
window.addEventListener(
|
||||
'onerror',
|
||||
this
|
||||
);
|
||||
window.addEventListener(
|
||||
'onverificationcode',
|
||||
this
|
||||
);
|
||||
},
|
||||
handleEvent: function c_handleEvent(e) {
|
||||
switch(e.type) {
|
||||
case 'init':
|
||||
_localize(e.detail.appName);
|
||||
UI.render(e.detail.candidates);
|
||||
break;
|
||||
case 'shown':
|
||||
UI.setScroll();
|
||||
break;
|
||||
case 'onverifying':
|
||||
UI.onVerifying();
|
||||
break;
|
||||
case 'onverified':
|
||||
UI.onVerified(e.detail.phoneNumber);
|
||||
break;
|
||||
case 'onerror':
|
||||
UI.onerror(e.detail.error);
|
||||
break;
|
||||
case 'onverificationcode':
|
||||
UI.onVerificationCode(e.detail);
|
||||
break;
|
||||
}
|
||||
},
|
||||
requestCode: function c_requestCode() {
|
||||
window.parent.MobileIdManager.requestNewCode();
|
||||
},
|
||||
postIdentity: function c_postIdentity(params) {
|
||||
window.parent.MobileIdManager.sendMsisdn(params);
|
||||
},
|
||||
postVerificationCode: function c_postIdentity(params) {
|
||||
window.parent.MobileIdManager.sendVerificationCode(params);
|
||||
},
|
||||
postCloseAction: function c_postCloseAction(isVerified) {
|
||||
window.parent.MobileIdManager.close(isVerified);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Controller = Controller;
|
||||
|
||||
}(this));
|
||||
60
gaia/apps/system/mobile_id/js/error_overlay.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global HtmlHelper */
|
||||
/* exported MobileIDErrorOverlay */
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* MobileIDErrorOverlay shows an error prompt to the user with the info
|
||||
* retrieved after getting an error.
|
||||
* As params, we can define Title and Message.
|
||||
*/
|
||||
|
||||
var MobileIDErrorOverlay = {
|
||||
init: function mobileid_error_overlay_init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
HtmlHelper.importElements(this,
|
||||
'mobileid-error-overlay',
|
||||
'mobileid-error-title',
|
||||
'mobileid-error-msg',
|
||||
'mobileid-error-ok'
|
||||
);
|
||||
|
||||
this.mobileidErrorOverlay.addEventListener(
|
||||
'submit',
|
||||
this.prevent.bind(this)
|
||||
);
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
|
||||
show: function mobileidm_error_overlay_show(titleL10n, messageL10n, cb) {
|
||||
this.init();
|
||||
|
||||
this.mobileidErrorTitle.setAttribute('data-l10n-id', titleL10n);
|
||||
this.mobileidErrorMsg.setAttribute('data-l10n-id', messageL10n);
|
||||
this.mobileidErrorOk.onclick = function(event) {
|
||||
if (typeof cb === 'function') {
|
||||
cb();
|
||||
}
|
||||
this.hide();
|
||||
}.bind(this);
|
||||
|
||||
this.mobileidErrorOverlay.classList.add('show');
|
||||
},
|
||||
|
||||
hide: function mobileidm_overlay_hide() {
|
||||
this.mobileidErrorOk.onclick = null;
|
||||
this.mobileidErrorOverlay.classList.remove('show');
|
||||
},
|
||||
|
||||
prevent: function mobileidm_prevent(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
8
gaia/apps/system/mobile_id/js/mobile_id.js
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
/* global UI, Controller */
|
||||
|
||||
'use strict';
|
||||
|
||||
window.onload = function() {
|
||||
Controller.init();
|
||||
UI.init();
|
||||
};
|
||||
620
gaia/apps/system/mobile_id/js/ui.js
Executable file
|
|
@ -0,0 +1,620 @@
|
|||
|
||||
/* exports UI */
|
||||
/* global Controller, MobileIDErrorOverlay*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
var initialized;
|
||||
|
||||
var allowButton, header, verificationCodeButton,
|
||||
multistateButton, panelsContainer,
|
||||
verificationCodeInput, msisdnInput,
|
||||
msisdnAutomaticOptions, typeMSISDNButton,
|
||||
selectAutomaticOptionsButton, msisdnContainer,
|
||||
legend, legendParent,
|
||||
countryCodesSelect, verificationPanel,
|
||||
msisdnSelectionPanel, verificationCodeTimer,
|
||||
stepsExplanation, verificationExplanation,
|
||||
successExplanation;
|
||||
|
||||
var isVerified = false;
|
||||
var isVerifying = false;
|
||||
var isVerificationCode = false;
|
||||
var isTimeoutOver = false;
|
||||
var isManualMSISDN = false;
|
||||
var buttonCurrentStatus;
|
||||
|
||||
var appName, identity;
|
||||
|
||||
var verificationInterval, verificationIntervalSteps = 0,
|
||||
currentIntervalStep = 0;
|
||||
|
||||
var countryCodes;
|
||||
|
||||
function _setMultibuttonStep(stepName) {
|
||||
buttonCurrentStatus = stepName;
|
||||
var shift = 0;
|
||||
switch(stepName) {
|
||||
case 'allow':
|
||||
shift = 0;
|
||||
allowButton.className = 'msb-button-step state-allow';
|
||||
allowButton.disabled = false;
|
||||
break;
|
||||
case 'sending':
|
||||
shift = 0;
|
||||
allowButton.className = 'msb-button-step state-sending';
|
||||
allowButton.disabled = true;
|
||||
break;
|
||||
case 'verify':
|
||||
shift = -50;
|
||||
verificationCodeButton.className = 'msb-button-step state-verify';
|
||||
verificationCodeButton.disabled = false;
|
||||
break;
|
||||
case 'verifying':
|
||||
shift = -50;
|
||||
verificationCodeButton.className = 'msb-button-step state-verifying';
|
||||
verificationCodeButton.disabled = true;
|
||||
break;
|
||||
case 'verified':
|
||||
shift = -50;
|
||||
verificationCodeButton.className = 'msb-button-step state-verified';
|
||||
verificationCodeButton.disabled = false;
|
||||
break;
|
||||
case 'resend':
|
||||
shift = -50;
|
||||
verificationCodeButton.className = 'msb-button-step state-resend';
|
||||
verificationCodeButton.disabled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
multistateButton.style.transform = 'translateX(' + shift + '%)';
|
||||
}
|
||||
|
||||
function _setPanelsStep(stepName) {
|
||||
var shift = 0;
|
||||
switch(stepName) {
|
||||
case 'msisdn':
|
||||
shift = 0;
|
||||
break;
|
||||
case 'verification':
|
||||
shift = -25;
|
||||
break;
|
||||
case 'done':
|
||||
shift = -50;
|
||||
break;
|
||||
}
|
||||
|
||||
panelsContainer.style.transform = 'translateX(' + shift + '%)';
|
||||
}
|
||||
|
||||
function _disablePanel(stepName) {
|
||||
switch(stepName) {
|
||||
case 'msisdn':
|
||||
msisdnAutomaticOptions.classList.add('disabled');
|
||||
typeMSISDNButton.classList.add('disabled');
|
||||
selectAutomaticOptionsButton.classList.add('disabled');
|
||||
countryCodesSelect.classList.add('disabled');
|
||||
msisdnInput.disabled = true;
|
||||
break;
|
||||
case 'verification':
|
||||
verificationCodeInput.disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function _enablePanel(stepName) {
|
||||
switch(stepName) {
|
||||
case 'msisdn':
|
||||
msisdnAutomaticOptions.classList.remove('disabled');
|
||||
typeMSISDNButton.classList.remove('disabled');
|
||||
selectAutomaticOptionsButton.classList.remove('disabled');
|
||||
countryCodesSelect.classList.remove('disabled');
|
||||
msisdnInput.disabled = false;
|
||||
break;
|
||||
case 'verification':
|
||||
verificationCodeInput.disabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function _msisdnContainerTranslate(step) {
|
||||
isManualMSISDN = step === 0 ? false : true;
|
||||
msisdnContainer.style.transform = 'translateX(' + -1 * 50 * step + '%)';
|
||||
}
|
||||
|
||||
function _fieldErrorDance(element) {
|
||||
element.addEventListener('animationend',function danceOver() {
|
||||
element.removeEventListener('animationend', danceOver);
|
||||
element.classList.remove('error');
|
||||
});
|
||||
element.classList.add('error');
|
||||
}
|
||||
|
||||
function _fillCountryCodesList(callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function fileLoaded() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 0 || xhr.status === 200) {
|
||||
// Cache the CC
|
||||
countryCodes = xhr.response;
|
||||
|
||||
var sortedObject = _sortCountriesByFullName();
|
||||
// Clean the <select> element
|
||||
countryCodesSelect.innerHTML = '';
|
||||
// Per country, we show its country name (`full`)
|
||||
var ccFragment = document.createDocumentFragment();
|
||||
var added = {};
|
||||
sortedObject.forEach(function(country) {
|
||||
var mcc = country[0];
|
||||
// Do not re-add countries (like USA, which has more than one mcc)
|
||||
if (added[countryCodes[mcc].code]) {
|
||||
return;
|
||||
}
|
||||
var option = document.createElement('option');
|
||||
option.textContent = countryCodes[mcc].full + ' (' +
|
||||
countryCodes[mcc].prefix + ')';
|
||||
option.value = mcc;
|
||||
added[countryCodes[mcc].code] = true;
|
||||
ccFragment.appendChild(option);
|
||||
});
|
||||
|
||||
countryCodesSelect.appendChild(ccFragment);
|
||||
|
||||
// Once we have the country code list we can default to the current
|
||||
// MCC if available.
|
||||
var conn = window.navigator.mozMobileConnections ?
|
||||
window.navigator.mozMobileConnections[0] : null;
|
||||
if (conn && conn.voice && conn.voice.network) {
|
||||
countryCodesSelect.value = conn.voice.network.mcc;
|
||||
legend.innerHTML = countryCodes[countryCodesSelect.value].prefix;
|
||||
}
|
||||
callback();
|
||||
} else {
|
||||
console.error('Failed to fetch file: ', xhr.statusText);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open('GET', ' /shared/resources/mcc.json', true);
|
||||
xhr.responseType = 'json';
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function _sortCountriesByFullName() {
|
||||
var sorted = [];
|
||||
for (var mcc in countryCodes) {
|
||||
sorted.push([mcc, countryCodes[mcc].full]);
|
||||
}
|
||||
sorted = sorted.sort(function compareFunction(a, b) {
|
||||
return a[1] > b[1];
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function _getIdentitySelected() {
|
||||
var identity;
|
||||
if (isManualMSISDN) {
|
||||
identity = {
|
||||
prefix: countryCodes[countryCodesSelect.value].prefix,
|
||||
mcc: countryCodesSelect.value,
|
||||
phoneNumber: msisdnInput.value.length && msisdnInput.value || null
|
||||
};
|
||||
} else {
|
||||
var query = 'input[name="msisdn-option"]:checked';
|
||||
var optionChecked = document.querySelector(query);
|
||||
if (optionChecked.dataset.identificationType === 'msisdn') {
|
||||
identity = {
|
||||
prefix: null,
|
||||
mcc: null,
|
||||
phoneNumber: optionChecked.value
|
||||
};
|
||||
} else {
|
||||
identity = {
|
||||
serviceId: optionChecked.dataset.serviceId
|
||||
};
|
||||
}
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
function _getCode() {
|
||||
return verificationCodeInput.value;
|
||||
}
|
||||
|
||||
function _showVerificationPanel(phoneNumber) {
|
||||
// If our identity is registered properly, we are ready to go!
|
||||
isVerificationCode = false;
|
||||
isVerifying = false;
|
||||
isVerified = true;
|
||||
clearInterval(verificationInterval);
|
||||
// Update the status of the button showing the 'success'
|
||||
_setMultibuttonStep('verified');
|
||||
// Remove the progress bar
|
||||
verificationCodeTimer.classList.remove('show');
|
||||
// Update the string
|
||||
navigator.mozL10n.setAttributes(
|
||||
successExplanation,
|
||||
'successMessage',
|
||||
{
|
||||
phone_number: phoneNumber || '',
|
||||
app_name: appName
|
||||
}
|
||||
);
|
||||
// Add gif animation at the end
|
||||
panelsContainer.addEventListener('transitionend', function readyToGif() {
|
||||
panelsContainer.removeEventListener('transitionend', readyToGif);
|
||||
document.querySelector('.done-panel').classList.add('done-panel-gif');
|
||||
});
|
||||
// Show the panel with some feedback to the user
|
||||
_setPanelsStep('done');
|
||||
}
|
||||
|
||||
function _addEventListeners() {
|
||||
header.addEventListener(
|
||||
'action',
|
||||
function onClose() {
|
||||
Controller.postCloseAction(isVerified);
|
||||
}
|
||||
);
|
||||
|
||||
typeMSISDNButton.addEventListener(
|
||||
'click',
|
||||
function onManualMSISDN() {
|
||||
_msisdnContainerTranslate(1);
|
||||
}
|
||||
);
|
||||
|
||||
selectAutomaticOptionsButton.addEventListener(
|
||||
'click',
|
||||
function onAutomaticMSISDN() {
|
||||
_msisdnContainerTranslate(0);
|
||||
}
|
||||
);
|
||||
|
||||
allowButton.addEventListener(
|
||||
'click',
|
||||
function onAllow() {
|
||||
// Send to controller the identity selected
|
||||
identity = _getIdentitySelected();
|
||||
// Check if is a valid identity
|
||||
if (identity.mcc &&
|
||||
(!identity.phoneNumber ||
|
||||
isNaN(identity.phoneNumber)
|
||||
)) {
|
||||
_fieldErrorDance(msisdnInput);
|
||||
return;
|
||||
}
|
||||
// Disable to avoid any action while requesting the server
|
||||
_disablePanel('msisdn');
|
||||
// Update the status of the button
|
||||
_setMultibuttonStep('sending');
|
||||
// Post identity
|
||||
Controller.postIdentity(identity);
|
||||
}
|
||||
);
|
||||
|
||||
verificationCodeButton.addEventListener(
|
||||
'click',
|
||||
function onVerify() {
|
||||
// In the case is not verified yet
|
||||
if (!isVerified) {
|
||||
// Was the tap done in the 'resend'?
|
||||
if (verificationCodeButton.classList.contains('state-resend')) {
|
||||
Controller.requestCode();
|
||||
verificationCodeInput.value = '';
|
||||
// Disable the panel
|
||||
_enablePanel('verification');
|
||||
// We udpate the button
|
||||
_setMultibuttonStep('sending');
|
||||
// As we are resending, we reset the conditions
|
||||
isVerifying = false;
|
||||
isTimeoutOver = false;
|
||||
} else {
|
||||
var code = _getCode();
|
||||
if (!code.length || isNaN(code) || code.length < 6) {
|
||||
_fieldErrorDance(verificationCodeInput);
|
||||
return;
|
||||
}
|
||||
// If we are in the proccess of verifying, we need
|
||||
// first to send the code to the server
|
||||
Controller.postVerificationCode(_getCode());
|
||||
// Disable the panel
|
||||
_disablePanel('verification');
|
||||
// We udpate the button
|
||||
_setMultibuttonStep('verifying');
|
||||
// As we are verifying, we udpate the flag
|
||||
isVerifying = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// If the identity posted to the server and/or the verification
|
||||
// code is accepted, we are ready to close the flow.
|
||||
Controller.postCloseAction(isVerified);
|
||||
}
|
||||
);
|
||||
|
||||
countryCodesSelect.addEventListener(
|
||||
'blur',
|
||||
function onSelectBlur() {
|
||||
countryCodesSelect.hidden = true;
|
||||
legend.innerHTML = countryCodes[countryCodesSelect.value].prefix;
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
legendParent.addEventListener(
|
||||
'click',
|
||||
function onLegendClick() {
|
||||
countryCodesSelect.click();
|
||||
countryCodesSelect.focus();
|
||||
countryCodesSelect.hidden = false;
|
||||
return;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var UI = {
|
||||
init: function ui_init(params) {
|
||||
var callback = params && params.callback ?
|
||||
params.callback :
|
||||
function() {};
|
||||
|
||||
header = document.getElementById('header');
|
||||
allowButton = document.getElementById('allow-button');
|
||||
verificationCodeButton = document.getElementById('verify-button');
|
||||
multistateButton = document.getElementById('msb');
|
||||
panelsContainer = document.getElementById('panels-container');
|
||||
verificationCodeInput = document.getElementById('verification-code');
|
||||
msisdnInput = document.getElementById('msisdn-input');
|
||||
msisdnAutomaticOptions = document.querySelector('.phone-options-list');
|
||||
typeMSISDNButton = document.getElementById('add-msisdn');
|
||||
selectAutomaticOptionsButton =
|
||||
document.getElementById('do-automatic-msisdn');
|
||||
msisdnContainer = document.querySelector('.msisdn-selection-wrapper');
|
||||
legend = document.getElementById('legend');
|
||||
legendParent = document.getElementById('legend-parent');
|
||||
countryCodesSelect = document.getElementById('country-codes-select');
|
||||
verificationPanel = document.querySelector('.verification-panel');
|
||||
msisdnSelectionPanel = document.querySelector('.msisdn-selection-panel');
|
||||
verificationCodeTimer =
|
||||
document.getElementById('verification-code-timer');
|
||||
// Elements to localize
|
||||
stepsExplanation =
|
||||
document.getElementById('mobile-id-explanation');
|
||||
verificationExplanation =
|
||||
document.getElementById('verification-code-explanation');
|
||||
successExplanation =
|
||||
document.getElementById('success-explanation');
|
||||
|
||||
// Fill the country code list
|
||||
_fillCountryCodesList(function() {
|
||||
// Avoid adding duplicated listeners if init is called more than once.
|
||||
if (initialized) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
_addEventListeners();
|
||||
callback();
|
||||
});
|
||||
|
||||
// HACK: We must reposition the gaia-header
|
||||
// text once we know the dialog is visible.
|
||||
window.addEventListener('shown', function() {
|
||||
var title = header.querySelector('h1');
|
||||
title.textContent = title.textContent;
|
||||
});
|
||||
},
|
||||
localize: function ui_localize(name) {
|
||||
// Cache the name of the app
|
||||
appName = name;
|
||||
// Let's localize the explanation
|
||||
navigator.mozL10n.setAttributes(
|
||||
stepsExplanation,
|
||||
'mobileIDExplanation',
|
||||
{
|
||||
app_name: appName
|
||||
}
|
||||
);
|
||||
},
|
||||
render: function ui_render(identifications) {
|
||||
var optionsFragment = document.createDocumentFragment();
|
||||
|
||||
if (!identifications || !identifications.length) {
|
||||
_msisdnContainerTranslate(1);
|
||||
selectAutomaticOptionsButton.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var isPrimaryOption = false;
|
||||
for (var i = 0, l = identifications.length; i < l; i++) {
|
||||
var li = document.createElement('li');
|
||||
var label = document.createElement('label');
|
||||
var typeIcon = document.createElement('span');
|
||||
var radio = document.createElement('input');
|
||||
var radioMask = document.createElement('span');
|
||||
var name = document.createElement('p');
|
||||
|
||||
var iconClasses = 'icon icon-simcardlock';
|
||||
if (identifications[i].primary) {
|
||||
isPrimaryOption = true;
|
||||
radio.checked = true;
|
||||
iconClasses+=' primary';
|
||||
} else {
|
||||
iconClasses+=' sim' + (+identifications[i].serviceId + 1);
|
||||
}
|
||||
typeIcon.className = iconClasses;
|
||||
|
||||
name.removeAttribute('data-l10n-id');
|
||||
name.textContent =
|
||||
identifications[i].msisdn ||
|
||||
identifications[i].operator;
|
||||
if (!name.textContent) {
|
||||
navigator.mozL10n.setAttributes(name, 'simLabel', {
|
||||
id: +identifications[i].serviceId + 1 });
|
||||
}
|
||||
|
||||
radio.name = 'msisdn-option';
|
||||
radio.type = 'radio';
|
||||
|
||||
if (identifications[i].serviceId !== undefined) {
|
||||
radio.dataset.identificationType = 'serviceid';
|
||||
radio.dataset.serviceId = identifications[i].serviceId;
|
||||
} else {
|
||||
radio.dataset.identificationType = 'msisdn';
|
||||
}
|
||||
|
||||
radio.value =
|
||||
identifications[i].msisdn || identifications[i].serviceId;
|
||||
radioMask.className = 'radio-mask';
|
||||
|
||||
label.appendChild(typeIcon);
|
||||
label.appendChild(radio);
|
||||
label.appendChild(radioMask);
|
||||
label.appendChild(name);
|
||||
|
||||
li.appendChild(label);
|
||||
|
||||
optionsFragment.appendChild(li);
|
||||
}
|
||||
|
||||
var phoneOptionsList = document.querySelector('.phone-options-list');
|
||||
|
||||
// If no option is checked, I check the first one as default
|
||||
if (!isPrimaryOption && identifications.length > 0) {
|
||||
optionsFragment.children[0].querySelector('input').checked = true;
|
||||
}
|
||||
|
||||
phoneOptionsList.innerHTML = '';
|
||||
phoneOptionsList.appendChild(optionsFragment);
|
||||
},
|
||||
onVerifying: function ui_onverifiying() {
|
||||
// Update the button. There is no panel change
|
||||
_setMultibuttonStep('verifying');
|
||||
_disablePanel('verification');
|
||||
},
|
||||
onVerified: function ui_onverified(phoneNumber) {
|
||||
if (isVerificationCode && !isVerifying) {
|
||||
_disablePanel('verification');
|
||||
verificationCodeInput.type = 'password';
|
||||
verificationCodeInput.value = 'FAKECODE';
|
||||
setTimeout(_showVerificationPanel.bind(this, phoneNumber), 1000);
|
||||
} else {
|
||||
_showVerificationPanel(phoneNumber);
|
||||
}
|
||||
|
||||
},
|
||||
onVerificationCode: function ui_onVerificationCode(params) {
|
||||
isVerificationCode = true;
|
||||
// Update the status of the button
|
||||
_setMultibuttonStep('verify');
|
||||
// Show the verification code panel
|
||||
_setPanelsStep('verification');
|
||||
_enablePanel('verification');
|
||||
|
||||
// Update the string
|
||||
navigator.mozL10n.setAttributes(
|
||||
verificationExplanation,
|
||||
'verificationCodeExplanation',
|
||||
{
|
||||
phone_number: identity.phoneNumber
|
||||
}
|
||||
);
|
||||
|
||||
// Timer UI
|
||||
|
||||
// Update the params we need
|
||||
isTimeoutOver = false;
|
||||
currentIntervalStep = +params.verificationTimeoutLeft;
|
||||
verificationIntervalSteps = +params.verificationTimeout;
|
||||
// Update the UI properly as starting poing
|
||||
verificationCodeTimer.max = verificationIntervalSteps;
|
||||
verificationCodeTimer.value = currentIntervalStep;
|
||||
// Add the timeout ui
|
||||
verificationCodeTimer.classList.add('show');
|
||||
// Boot the interval with the number of steps we need
|
||||
verificationInterval = setInterval(function() {
|
||||
--currentIntervalStep;
|
||||
if (currentIntervalStep < 0) {
|
||||
if (!isVerifying) {
|
||||
// Show 'resend' button
|
||||
_setMultibuttonStep('resend');
|
||||
_disablePanel('verification');
|
||||
}
|
||||
// Hide the keyboard if present
|
||||
document.activeElement.blur();
|
||||
// Set that the timeout is over
|
||||
isTimeoutOver = true;
|
||||
// Hide timer
|
||||
verificationCodeTimer.classList.remove('show');
|
||||
// Clear interval
|
||||
clearInterval(verificationInterval);
|
||||
return;
|
||||
}
|
||||
verificationCodeTimer.value = currentIntervalStep;
|
||||
}, 1000);
|
||||
|
||||
// TODO Add retries when available in the server
|
||||
|
||||
},
|
||||
onerror: function ui_onError(error) {
|
||||
switch (error) {
|
||||
case 'VERIFICATION_CODE_TIMEOUT':
|
||||
case 'NO_RETRIES_LEFT':
|
||||
MobileIDErrorOverlay.show(
|
||||
'errorTitle',
|
||||
'timeoutErrorMessage'
|
||||
);
|
||||
_disablePanel('verification');
|
||||
_setMultibuttonStep('resend');
|
||||
break;
|
||||
case 'INVALID_PHONE_NUMBER':
|
||||
_enablePanel('msisdn');
|
||||
_setMultibuttonStep('allow');
|
||||
_fieldErrorDance(msisdnInput);
|
||||
break;
|
||||
case 'INVALID_VERIFICATION_CODE':
|
||||
if (isTimeoutOver) {
|
||||
_disablePanel('verification');
|
||||
_setMultibuttonStep('resend');
|
||||
isTimeoutOver = false;
|
||||
isVerifying = false;
|
||||
return;
|
||||
}
|
||||
_enablePanel('verification');
|
||||
_setMultibuttonStep('verify');
|
||||
_fieldErrorDance(verificationCodeInput);
|
||||
break;
|
||||
default:
|
||||
MobileIDErrorOverlay.show(
|
||||
'errorTitle',
|
||||
'serverErrorMessage',
|
||||
function onClick() {
|
||||
Controller.postCloseAction(isVerified);
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
setScroll: function ui_setScroll() {
|
||||
// Add scroll management to show properly the input
|
||||
// when the keyboard is shown
|
||||
var bodyRect = document.body.getBoundingClientRect(),
|
||||
codeRect = verificationCodeInput.getBoundingClientRect(),
|
||||
msisdnInputRect = msisdnInput.getBoundingClientRect(),
|
||||
offsetCode = codeRect.top - bodyRect.top,
|
||||
offsetMSISDN = msisdnInputRect.top - bodyRect.top;
|
||||
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
function onResized() {
|
||||
verificationPanel.scrollTop = offsetCode;
|
||||
msisdnSelectionPanel.scrollTop = offsetMSISDN;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
exports.UI = UI;
|
||||
|
||||
}(this));
|
||||
26
gaia/apps/system/mobile_id/locales/mobile_id.en-US.properties
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
|
||||
# Mobile Identity
|
||||
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#
|
||||
|
||||
# Button status
|
||||
share = Share
|
||||
resend = Resend
|
||||
connecting = Connecting
|
||||
verify = Verify
|
||||
verifying = Verifying
|
||||
verified = Verified
|
||||
|
||||
# Explanation of every step
|
||||
mobileIDExplanation = {{app_name}} would like to know and verify your phone number. Please choose which one you want to share or enter a new one.
|
||||
verificationCodeExplanation = We’ve sent a code to {{phone_number}}. We’ll try to pick it up automatically, but you’ll have to enter it manually if it’s not in use in this device.
|
||||
successMessage = Awesome! Your number {{phone_number}} is now verified and available to {{app_name}}.
|
||||
|
||||
# Errors
|
||||
serverErrorMessage = The verification server is unavailable at this time. Please check your Internet connection, try again or wait a few minutes.
|
||||
timeoutErrorMessage = There seems to be a problem with your verification code. Let's try once again!
|
||||
errorTitle = Mobile identity
|
||||
ok = OK
|
||||
|
||||
# Options when typing a phone number
|
||||
addPhoneNumber = Or add your phone number
|
||||
selectPhoneNumber = Or select a phone number from the list
|
||||
45
gaia/apps/system/mobile_id/style/error_overlay.css
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
#mobileid-error-overlay {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 2rem;
|
||||
background-color: rgba(51, 51, 51, 0.9);
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#mobileid-error-overlay.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
form[role=dialog] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form[role=dialog].show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
form[role="dialog"][data-type="confirm"]:before {
|
||||
content: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mobileid-error-overlay section{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 14rem 0 5rem 0;
|
||||
}
|
||||
|
||||
form[role="dialog"][data-type="confirm"] p {
|
||||
max-height: calc(100% - 9rem);
|
||||
overflow: scroll;
|
||||
}
|
||||
BIN
gaia/apps/system/mobile_id/style/images/Telf_tick.png
Executable file
|
After Width: | Height: | Size: 519 B |
BIN
gaia/apps/system/mobile_id/style/images/Telf_tick@1.5x.png
Executable file
|
After Width: | Height: | Size: 718 B |
BIN
gaia/apps/system/mobile_id/style/images/Telf_tick@2.25x.png
Executable file
|
After Width: | Height: | Size: 1 KiB |
BIN
gaia/apps/system/mobile_id/style/images/Telf_tick@2x.png
Executable file
|
After Width: | Height: | Size: 935 B |
BIN
gaia/apps/system/mobile_id/style/images/animation.gif
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
gaia/apps/system/mobile_id/style/images/checked.png
Executable file
|
After Width: | Height: | Size: 292 B |
BIN
gaia/apps/system/mobile_id/style/images/checked@1.5x.png
Executable file
|
After Width: | Height: | Size: 397 B |
BIN
gaia/apps/system/mobile_id/style/images/checked@2x.png
Executable file
|
After Width: | Height: | Size: 650 B |
BIN
gaia/apps/system/mobile_id/style/images/simcards.png
Executable file
|
After Width: | Height: | Size: 717 B |
BIN
gaia/apps/system/mobile_id/style/images/warning.png
Executable file
|
After Width: | Height: | Size: 238 B |
436
gaia/apps/system/mobile_id/style/mobile_id.css
Executable file
|
|
@ -0,0 +1,436 @@
|
|||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 10px;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: hidden !important;
|
||||
font-family: 'MozTT', sans-serif;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
section[role="region"] {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #ccc;
|
||||
text-decoration: none !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.disabled *{
|
||||
text-decoration: none !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* We have a 'magic' button which translates with us through the
|
||||
* proccess. This is called 'multistate-button' or 'msb'
|
||||
*/
|
||||
|
||||
.multistate-button {
|
||||
width: 200%;
|
||||
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
transition: all .3s ease;
|
||||
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.msb-button-step {
|
||||
height: 4rem;
|
||||
width: calc(50% - 2rem);
|
||||
|
||||
float: left;
|
||||
margin: 0 1rem;
|
||||
|
||||
background: #00AACC;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 2rem;
|
||||
line-height: 4rem;
|
||||
text-decoration: none;
|
||||
font-size: 1.8rem;
|
||||
font-style: italic;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.msb-button-step progress {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some buttons which have different states
|
||||
*/
|
||||
|
||||
.states-wrapper {
|
||||
height: 12rem;
|
||||
transform: translateY(-4rem);
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.state-resend .states-wrapper div span:first-child{
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
background: url(images/warning.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.state-allow .states-wrapper{
|
||||
transform: translateY(0);
|
||||
}
|
||||
.state-connecting .states-wrapper{
|
||||
transform: translateY(-4rem);
|
||||
}
|
||||
.state-resend .states-wrapper{
|
||||
transform: translateY(0);
|
||||
}
|
||||
.state-verify .states-wrapper{
|
||||
transform: translateY(-4rem);
|
||||
}
|
||||
.state-verifying .states-wrapper{
|
||||
transform: translateY(-8rem);
|
||||
}
|
||||
.state-verified > .states-wrapper{
|
||||
transform: translateY(-12rem);
|
||||
}
|
||||
|
||||
.state-verified {
|
||||
background: #5F9B0A;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.state-resend {
|
||||
background: #B90000 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
We will contain the three main panels of the Mobile ID flow:
|
||||
- [1] Selection of the phone number
|
||||
- [2] Add verification code (if required)
|
||||
- [3] Feedback screen
|
||||
*/
|
||||
|
||||
#verification-code-timer {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#verification-code-timer.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.panels-container {
|
||||
height: calc(100% - 11.3rem);
|
||||
width: 400%;
|
||||
overflow: hidden;
|
||||
|
||||
transition: all .6s ease;
|
||||
}
|
||||
|
||||
.panel {
|
||||
height: 100%;
|
||||
width: 25%;
|
||||
|
||||
float: left;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: scroll;
|
||||
|
||||
background: white;
|
||||
}
|
||||
|
||||
p.explanation {
|
||||
font-size: 1.9rem;
|
||||
font-weight: 300;
|
||||
line-height: 2.2rem;
|
||||
}
|
||||
|
||||
div, span, p, label {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
[1] Selection of the phone number
|
||||
*/
|
||||
|
||||
.msisdn-selection-panel p.explanation {
|
||||
padding: 2rem 1rem 1rem 2rem;
|
||||
font-size: 1.9rem;
|
||||
font-weight: 300;
|
||||
line-height: 2.2rem;
|
||||
}
|
||||
|
||||
.msisdn-overflow-shield {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msisdn-selection-wrapper {
|
||||
width: 200%;
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.msisdn-selection-wrapper div{
|
||||
padding: 0 2rem 1rem 2rem;
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/*
|
||||
* We can select one of the pre-popopulated MSISDN
|
||||
*/
|
||||
|
||||
ul.phone-options-list {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
list-style: none;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
ul.phone-options-list li {
|
||||
height: 5rem;
|
||||
position: relative;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
vertical-align: baseline;
|
||||
font-size: 1.7rem;
|
||||
line-height: 5.5rem;
|
||||
border-bottom: .1rem solid #ccc;
|
||||
}
|
||||
|
||||
ul.phone-options-list li:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul.phone-options-list li input {
|
||||
display: none;
|
||||
float: right;
|
||||
}
|
||||
|
||||
ul.phone-options-list li span.icon {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
float: left;
|
||||
margin-top: 1rem;
|
||||
margin-right: 1rem;
|
||||
|
||||
background-image: url(images/simcards.png);
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
ul.phone-options-list li span.sim1 {
|
||||
background-image: url(images/simcards.png);
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
ul.phone-options-list li span.sim2 {
|
||||
background-image: url(images/simcards.png);
|
||||
background-position: 0 -3rem;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
ul.phone-options-list li span.primary {
|
||||
background-image: url(images/Telf_tick.png);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
|
||||
ul.phone-options-list li span.radio-mask {
|
||||
width: 5rem;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
ul.phone-options-list li input:checked + span.radio-mask {
|
||||
background-image: url(images/checked.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#add-msisdn {
|
||||
width: 100%;
|
||||
|
||||
margin-top: 2rem;
|
||||
font-size: 1.5rem;
|
||||
color: #858585;
|
||||
text-decoration: underline;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/*
|
||||
If we type the MSISDN manually
|
||||
*/
|
||||
|
||||
#do-automatic-msisdn {
|
||||
width: 100%;
|
||||
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
font-size: 1.5rem;
|
||||
color: #858585;
|
||||
text-decoration: underline;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
ul.msisdn-form {
|
||||
width: 100%;
|
||||
|
||||
margin-top: 6rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.msisdn-form li{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
ul.msisdn-form li{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul.msisdn-form li button{
|
||||
height: 4rem;
|
||||
width: 5rem;
|
||||
padding-right: 2rem;
|
||||
text-align: center;
|
||||
line-height: 4.2rem;
|
||||
}
|
||||
|
||||
ul.msisdn-form li input{
|
||||
height: 4rem;
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
ul.msisdn-form li:last-child{
|
||||
width: calc(100% - 7.3rem);
|
||||
}
|
||||
|
||||
.button-select-wrapper {
|
||||
width: 7rem !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: unset !important;
|
||||
}
|
||||
|
||||
.button-select-wrapper select {
|
||||
width: calc(100% + 1.9rem) !important;
|
||||
font-size: 1.6rem;
|
||||
text-align: left;
|
||||
height: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
padding-left: .6rem !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*
|
||||
[2] Add verification code (if required)
|
||||
*/
|
||||
|
||||
.verification-panel {
|
||||
padding: 2rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.verification-panel p {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#verification-code {
|
||||
width: 100%;
|
||||
height: 5rem;
|
||||
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input.error {
|
||||
border: .1rem solid #B90000;
|
||||
animation: error-dance .5s;
|
||||
animation-iteration-count: 2;
|
||||
}
|
||||
|
||||
@keyframes error-dance {
|
||||
0% { transform: rotate(0); }
|
||||
25% { transform: rotate(1deg); }
|
||||
50% { transform: rotate(0); }
|
||||
75% { transform: rotate(1deg); }
|
||||
100% { transform: rotate(0deg); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
#verification-timer {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/*
|
||||
[3] Feedback screen
|
||||
*/
|
||||
|
||||
.done-panel {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 49%;
|
||||
background-position: center 60%;
|
||||
}
|
||||
|
||||
.done-panel-gif {
|
||||
background-image: url(images/animation.gif);
|
||||
}
|
||||
|
||||
.done-panel p {
|
||||
padding: 2rem 1rem;
|
||||
box-sizing: border-box;
|
||||
font-size: 2.2rem;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
#legend {
|
||||
text-align: center;
|
||||
display: block;
|
||||
padding-top: 0.8rem;
|
||||
padding-left: 1.3rem;
|
||||
}
|
||||