MOBILE-3565 login: Implement signup page

This commit is contained in:
Dani Palou 2020-10-29 13:45:55 +01:00
parent 232669855a
commit a6467f5073
10 changed files with 743 additions and 17 deletions

View File

@ -51,6 +51,10 @@ const routes: Routes = [
path: 'sitepolicy',
loadChildren: () => import('./pages/site-policy/site-policy.module').then( m => m.CoreLoginSitePolicyPageModule),
},
{
path: 'emailsignup',
loadChildren: () => import('./pages/email-signup/email-signup.module').then( m => m.CoreLoginEmailSignupPageModule),
},
];
@NgModule({

View File

@ -33,7 +33,10 @@
</ion-item>
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">
<core-show-password [name]="'password'">
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" core-show-password [clearOnEdit]="false"></ion-input>
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
formControlName="password" [clearOnEdit]="false" autocomplete="current-password" enterkeyhint="go"
required="true">
</ion-input>
</core-show-password>
</ion-item>
<ion-button expand="block" type="submit" [disabled]="siteChecked && !isBrowserSSO && !credForm.valid" class="ion-margin core-login-login-button">{{ 'core.login.loginbutton' | translate }}</ion-button>
@ -72,7 +75,7 @@
<ion-item class="ion-text-wrap" lines="none" *ngIf="authInstructions">
<ion-label><p><core-format-text [text]="authInstructions" [filter]="false"></core-format-text></p></ion-label>
</ion-item>
<ion-button expand="block" class="ion-margin" color="light" (onClick)="signup()">
<ion-button expand="block" class="ion-margin" color="light" router-direction="forward" routerLink="/login/emailsignup" [queryParams]="{siteUrl: siteUrl}">
{{ 'core.login.startsignup' | translate }}
</ion-button>
</ion-list>

View File

@ -279,13 +279,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
}
}
/**
* Signup button was clicked.
*/
signup(): void {
// @todo Go to signup.
}
/**
* Show instructions and scan QR code.
*/

View File

@ -0,0 +1,238 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>{{ 'core.login.newaccount' | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button *ngIf="authInstructions" (click)="showAuthInstructions()"
[attr.aria-label]="'core.login.instructions' | translate">
<ion-icon slot="icon-only" name="far-question-circle"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!settingsLoaded || isMinor" (ionRefresh)="refreshSettings($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="settingsLoaded" *ngIf="!isMinor">
<!-- Site has an unsupported required field. -->
<ion-list *ngIf="!allRequiredSupported">
<ion-item class="ion-text-wrap">
<ion-label>
{{ 'core.login.signuprequiredfieldnotsupported' | translate }}
</ion-label>
</ion-item>
<ion-button expand="block" class="ion-margin" [href]="signupUrl" core-link autoLogin="no">
{{ 'core.openinbrowser' | translate }}
</ion-button>
</ion-list>
<!-- Age verification. -->
<form ion-list *ngIf="allRequiredSupported && settingsLoaded && settings && ageDigitalConsentVerification"
[formGroup]="ageVerificationForm" (ngSubmit)="verifyAge($event)" #ageForm>
<ion-item-divider class="ion-text-wrap">
<ion-label><h3>{{ 'core.agelocationverification' | translate }}</h3></ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.whatisyourage' | translate }}</span>
</ion-label>
<ion-input type="number" name="age" placeholder="0" formControlName="age" autocapitalize="none" autocorrect="off">
</ion-input>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.wheredoyoulive' | translate }}</span>
</ion-label>
<ion-select name="country" formControlName="country" [placeholder]="'core.login.selectacountry' | translate">
<ion-select-option value="">{{ 'core.login.selectacountry' | translate }}</ion-select-option>
<ion-select-option *ngFor="let country of countries" [value]="country.code">{{country.name}}</ion-select-option>
</ion-select>
</ion-item>
<!-- Submit button. -->
<ion-button expand="block" class="ion-margin" type="submit" [disabled]="!ageVerificationForm.valid">
{{ 'core.proceed' | translate }}
</ion-button>
<ion-item class="ion-text-wrap">
<ion-label>
<p class="item-heading">{{ 'core.whyisthisrequired' | translate }}</p>
<p>{{ 'core.explanationdigitalminor' | translate }}</p>
</ion-label>
</ion-item>
</form>
<!-- Signup form. -->
<form ion-list *ngIf="allRequiredSupported && settingsLoaded && settings && !ageDigitalConsentVerification"
[formGroup]="signupForm" (ngSubmit)="create($event)" #signupFormEl>
<ion-item class="ion-text-wrap ion-text-center">
<ion-label>
<!-- If no sitename show big siteurl. -->
<p *ngIf="!siteName" class="ion-padding item-heading">{{siteUrl}}</p>
<!-- If sitename, show big sitename and small siteurl. -->
<p *ngIf="siteName" class="ion-padding item-heading">
<core-format-text [text]="siteName" [filter]="false"></core-format-text>
</p>
<p *ngIf="siteName">{{siteUrl}}</p>
</ion-label>
</ion-item>
<!-- Username and password. -->
<ion-item-divider class="ion-text-wrap">
<ion-label>{{ 'core.login.createuserandpass' | translate }}</ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.login.username' | translate }}</span>
</ion-label>
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}"
formControlName="username" autocapitalize="none" autocorrect="off">
</ion-input>
<core-input-errors [control]="signupForm.controls.username" [errorMessages]="usernameErrors"></core-input-errors>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.login.password' | translate }}</span>
</ion-label>
<core-show-password [name]="'password'">
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
formControlName="password" [clearOnEdit]="false" autocomplete="new-password" required="true">
</ion-input>
</core-show-password>
<p *ngIf="settings.passwordpolicy" class="core-input-footnote">
{{settings.passwordpolicy}}
</p>
<core-input-errors [control]="signupForm.controls.password" [errorMessages]="passwordErrors"></core-input-errors>
</ion-item>
<!-- More details. -->
<ion-item-divider class="ion-text-wrap">
<ion-label>
{{ 'core.login.supplyinfo' | translate }}
</ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.user.email' | translate }}</span>
</ion-label>
<ion-input type="email" name="email" placeholder="{{ 'core.user.email' | translate }}" formControlName="email"
autocapitalize="none" autocorrect="off">
</ion-input>
<core-input-errors [control]="signupForm.controls.email" [errorMessages]="emailErrors"></core-input-errors>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.user.emailagain' | translate }}</span>
</ion-label>
<ion-input type="email" name="email2" placeholder="{{ 'core.user.emailagain' | translate }}" autocapitalize="none"
formControlName="email2" autocorrect="off" [pattern]="escapeMail(signupForm.controls.email.value)">
</ion-input>
<core-input-errors [control]="signupForm.controls.email2" [errorMessages]="email2Errors"></core-input-errors>
</ion-item>
<ion-item *ngFor="let nameField of settings.namefields" class="ion-text-wrap">
<ion-label position="stacked">
<span core-mark-required="true">{{ 'core.user.' + nameField | translate }}</span>
</ion-label>
<ion-input type="text" name="nameField" placeholder="{{ 'core.user.' + nameField | translate }}"
formControlName="{{nameField}}" autocorrect="off">
</ion-input>
<core-input-errors [control]="signupForm.controls[nameField]" [errorMessages]="namefieldsErrors[nameField]">
</core-input-errors>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label position="stacked">{{ 'core.user.city' | translate }}</ion-label>
<ion-input type="text" name="city" placeholder="{{ 'core.user.city' | translate }}" formControlName="city"
autocorrect="off">
</ion-input>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label position="stacked" id="core-login-signup-country">{{ 'core.user.country' | translate }}</ion-label>
<ion-select name="country" formControlName="country" aria-labelledby="core-login-signup-country"
[placeholder]="'core.login.selectacountry' | translate">
<ion-select-option value="">{{ 'core.login.selectacountry' | translate }}</ion-select-option>
<ion-select-option *ngFor="let country of countries" [value]="country.code">{{country.name}}</ion-select-option>
</ion-select>
</ion-item>
<!-- Other categories. -->
<ng-container *ngFor="let category of categories">
<ion-item-divider class="ion-text-wrap">
<ion-label>{{ category.name }}</ion-label>
</ion-item-divider>
<!-- @todo <core-user-profile-field *ngFor="let field of category.fields" [field]="field" edit="true" signup="true"
registerAuth="email" [form]="signupForm"></core-user-profile-field> -->
</ng-container>
<!-- ReCAPTCHA -->
<ng-container *ngIf="settings.recaptchapublickey">
<ion-item-divider class="ion-text-wrap">
<ion-label>
<span [core-mark-required]="true">{{ 'core.login.security_question' | translate }}</span>
</ion-label>
</ion-item-divider>
<core-recaptcha [publicKey]="settings.recaptchapublickey" [model]="captcha" [siteUrl]="siteUrl"></core-recaptcha>
</ng-container>
<!-- Site policy (if any). -->
<ng-container *ngIf="settings.sitepolicy">
<ion-item-divider class="ion-text-wrap">
<ion-label>{{ 'core.login.policyagreement' | translate }}</ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap">
<ion-label>
<a [href]="settings.sitepolicy" core-link capture="false">
{{ 'core.login.policyagreementclick' | translate }}
</a>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-label>
<span [core-mark-required]="true">{{ 'core.login.policyaccept' | translate }}</span>
<core-input-errors [control]="signupForm.controls.policyagreed" [errorMessages]="policyErrors">
</core-input-errors>
</ion-label>
<ion-checkbox slot="end" name="policyagreed" formControlName="policyagreed"></ion-checkbox>
</ion-item>
</ng-container>
<!-- Submit button. -->
<ion-button expand="block" class="ion-margin" type="submit">{{ 'core.login.createaccount' | translate }}</ion-button>
<!-- Remove this once Ionic fixes this bug: https://github.com/ionic-team/ionic-framework/issues/19368 -->
<input type="submit" class="core-submit-hidden-enter" />
</form>
</core-loading>
<ion-list *ngIf="allRequiredSupported && isMinor">
<ion-item-divider class="ion-text-wrap">
<ion-label>
<p *ngIf="siteName" class="item-heading ion-padding">
<core-format-text [text]="siteName" [filter]="false"></core-format-text>
</p>
</ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap" lines="none">
<ion-label>
<p class="item-heading">{{ 'core.considereddigitalminor' | translate }}</p>
<p>{{ 'core.digitalminor_desc' | translate }}</p>
<p *ngIf="supportName">{{ supportName }}</p>
<p *ngIf="supportEmail">{{ supportEmail }}</p>
</ion-label>
</ion-item>
<ion-button *ngIf="!supportName && !supportEmail" expand="block" class="ion-margin" (click)="showContactOnSite()">
{{ 'core.openinbrowser' | translate }}
</ion-button>
</ion-list>
</ion-content>

View File

@ -0,0 +1,50 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreLoginEmailSignupPage } from './email-signup.page';
const routes: Routes = [
{
path: '',
component: CoreLoginEmailSignupPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CommonModule,
IonicModule,
TranslateModule.forChild(),
FormsModule,
ReactiveFormsModule,
CoreComponentsModule,
CoreDirectivesModule,
],
declarations: [
CoreLoginEmailSignupPage,
],
exports: [RouterModule],
})
export class CoreLoginEmailSignupPageModule {}

View File

@ -0,0 +1,419 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { NavController, IonContent, IonRefresher } from '@ionic/angular';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreCountry, CoreUtils } from '@services/utils/utils';
import { CoreWS, CoreWSExternalWarning } from '@services/ws';
import { AuthEmailSignupProfileFieldsCategory, AuthEmailSignupSettings, CoreLoginHelper } from '@core/login/services/helper';
import { CoreConstants } from '@core/constants';
import { Translate } from '@singletons/core.singletons';
import { CoreSitePublicConfigResponse } from '@classes/site';
/**
* Page to signup using email.
*/
@Component({
selector: 'page-core-login-email-signup',
templateUrl: 'email-signup.html',
styleUrls: ['../../login.scss'],
})
export class CoreLoginEmailSignupPage implements OnInit {
@ViewChild(IonContent) content?: IonContent;
@ViewChild('ageForm') ageFormElement?: ElementRef;
@ViewChild('signupFormEl') signupFormElement?: ElementRef;
signupForm: FormGroup;
siteUrl!: string;
siteConfig?: CoreSitePublicConfigResponse;
siteName?: string;
authInstructions?: string;
settings?: AuthEmailSignupSettings;
countries?: CoreCountry[];
categories?: AuthEmailSignupProfileFieldsCategory[];
settingsLoaded = false;
allRequiredSupported = true;
signupUrl?: string;
captcha = {
recaptcharesponse: '',
};
// Data for age verification.
ageVerificationForm: FormGroup;
countryControl: FormControl;
signUpCountryControl?: FormControl;
isMinor = false; // Whether the user is minor age.
ageDigitalConsentVerification?: boolean; // Whether the age verification is enabled.
supportName?: string;
supportEmail?: string;
// Validation errors.
usernameErrors: Record<string, string>;
passwordErrors: Record<string, string>;
emailErrors: Record<string, string>;
email2Errors: Record<string, string>;
policyErrors: Record<string, string>;
namefieldsErrors?: Record<string, Record<string, string>>;
constructor(
protected navCtrl: NavController,
protected fb: FormBuilder,
protected route: ActivatedRoute,
) {
// Create the ageVerificationForm.
this.ageVerificationForm = this.fb.group({
age: ['', Validators.required],
});
this.countryControl = this.fb.control('', Validators.required);
this.ageVerificationForm.addControl('country', this.countryControl);
// Create the signupForm with the basic controls. More controls will be added later.
this.signupForm = this.fb.group({
username: ['', Validators.required],
password: ['', Validators.required],
email: ['', Validators.compose([Validators.required, Validators.email])],
email2: ['', Validators.compose([Validators.required, Validators.email])],
});
// Setup validation errors.
this.usernameErrors = CoreLoginHelper.instance.getErrorMessages('core.login.usernamerequired');
this.passwordErrors = CoreLoginHelper.instance.getErrorMessages('core.login.passwordrequired');
this.emailErrors = CoreLoginHelper.instance.getErrorMessages('core.login.missingemail');
this.policyErrors = CoreLoginHelper.instance.getErrorMessages('core.login.policyagree');
this.email2Errors = CoreLoginHelper.instance.getErrorMessages(
'core.login.missingemail',
undefined,
'core.login.emailnotmatch',
);
}
/**
* Component initialized.
*/
ngOnInit(): void {
this.siteUrl = this.route.snapshot.queryParams['siteUrl'];
// Fetch the data.
this.fetchData().finally(() => {
this.settingsLoaded = true;
});
}
/**
* Complete the FormGroup using the settings received from server.
*/
protected completeFormGroup(): void {
this.signupForm.addControl('city', this.fb.control(this.settings?.defaultcity || ''));
this.signUpCountryControl = this.fb.control(this.settings?.country || '');
this.signupForm.addControl('country', this.signUpCountryControl);
// Add the name fields.
for (const i in this.settings?.namefields) {
this.signupForm.addControl(this.settings?.namefields[i], this.fb.control('', Validators.required));
}
if (this.settings?.sitepolicy) {
this.signupForm.addControl('policyagreed', this.fb.control(false, Validators.requiredTrue));
}
}
/**
* Fetch the required data from the server.
*
* @return Promise resolved when done.
*/
protected async fetchData(): Promise<void> {
try {
// Get site config.
this.siteConfig = await CoreSites.instance.getSitePublicConfig(this.siteUrl);
this.signupUrl = CoreTextUtils.instance.concatenatePaths(this.siteConfig.httpswwwroot, 'login/signup.php');
if (this.treatSiteConfig()) {
// Check content verification.
if (typeof this.ageDigitalConsentVerification == 'undefined') {
const result = await CoreUtils.instance.ignoreErrors(
CoreWS.instance.callAjax<IsAgeVerificationEnabledResponse>(
'core_auth_is_age_digital_consent_verification_enabled',
{},
{ siteUrl: this.siteUrl },
),
);
this.ageDigitalConsentVerification = !!result?.status;
}
await this.getSignupSettings();
}
this.completeFormGroup();
} catch (error) {
if (this.allRequiredSupported) {
CoreDomUtils.instance.showErrorModal(error);
}
}
}
/**
* Get signup settings from server.
*
* @return Promise resolved when done.
*/
protected async getSignupSettings(): Promise<void> {
this.settings = await CoreWS.instance.callAjax<AuthEmailSignupSettings>(
'auth_email_get_signup_settings',
{},
{ siteUrl: this.siteUrl },
);
// @todo userProfileFieldDelegate
this.categories = CoreLoginHelper.instance.formatProfileFieldsForSignup(this.settings.profilefields);
if (this.settings.recaptchapublickey) {
this.captcha.recaptcharesponse = ''; // Reset captcha.
}
if (!this.countryControl.value) {
this.countryControl.setValue(this.settings.country || '');
}
this.namefieldsErrors = {};
if (this.settings.namefields) {
this.settings.namefields.forEach((field) => {
this.namefieldsErrors![field] = CoreLoginHelper.instance.getErrorMessages('core.login.missing' + field);
});
}
this.countries = await CoreUtils.instance.getCountryListSorted();
}
/**
* Treat the site config, checking if it's valid and extracting the data we're interested in.
*
* @return True if success.
*/
protected treatSiteConfig(): boolean {
if (this.siteConfig?.registerauth == 'email' && !CoreLoginHelper.instance.isEmailSignupDisabled(this.siteConfig)) {
this.siteName = CoreConstants.CONFIG.sitename ? CoreConstants.CONFIG.sitename : this.siteConfig.sitename;
this.authInstructions = this.siteConfig.authinstructions;
this.ageDigitalConsentVerification = this.siteConfig.agedigitalconsentverification;
this.supportName = this.siteConfig.supportname;
this.supportEmail = this.siteConfig.supportemail;
this.countryControl.setValue(this.siteConfig.country || '');
return true;
} else {
CoreDomUtils.instance.showErrorModal(
Translate.instance.instant(
'core.login.signupplugindisabled',
{ $a: Translate.instance.instant('core.login.auth_email') },
),
);
this.navCtrl.pop();
return false;
}
}
/**
* Pull to refresh.
*
* @param event Event.
*/
refreshSettings(event?: CustomEvent<IonRefresher>): void {
this.fetchData().finally(() => {
event?.detail.complete();
});
}
/**
* Create account.
*
* @param e Event.
* @return Promise resolved when done.
*/
async create(e: Event): Promise<void> {
e.preventDefault();
e.stopPropagation();
if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) {
// Form not valid. Scroll to the first element with errors.
const errorFound = await CoreDomUtils.instance.scrollToInputError(this.content);
if (!errorFound) {
// Input not found, show an error modal.
CoreDomUtils.instance.showErrorModal('core.errorinvalidform', true);
}
return;
}
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
const params: Record<string, unknown> = {
username: this.signupForm.value.username.trim().toLowerCase(),
password: this.signupForm.value.password,
firstname: CoreTextUtils.instance.cleanTags(this.signupForm.value.firstname),
lastname: CoreTextUtils.instance.cleanTags(this.signupForm.value.lastname),
email: this.signupForm.value.email.trim(),
city: CoreTextUtils.instance.cleanTags(this.signupForm.value.city),
country: this.signupForm.value.country,
};
if (this.siteConfig?.launchurl) {
const service = CoreSites.instance.determineService(this.siteUrl);
params.redirect = CoreLoginHelper.instance.prepareForSSOLogin(this.siteUrl, service, this.siteConfig.launchurl);
}
// Get the recaptcha response (if needed).
if (this.settings?.recaptchapublickey && this.captcha.recaptcharesponse) {
params.recaptcharesponse = this.captcha.recaptcharesponse;
}
try {
// @todo Get the data for the custom profile fields.
const result = await CoreWS.instance.callAjax<SignupUserResult>(
'auth_email_signup_user',
params,
{ siteUrl: this.siteUrl },
);
if (result.success) {
CoreDomUtils.instance.triggerFormSubmittedEvent(this.signupFormElement, true);
// Show alert and ho back.
const message = Translate.instance.instant('core.login.emailconfirmsent', { $a: params.email });
CoreDomUtils.instance.showAlert(Translate.instance.instant('core.success'), message);
this.navCtrl.pop();
} else {
if (result.warnings && result.warnings.length) {
let error = result.warnings[0].message;
if (error == 'incorrect-captcha-sol') {
error = Translate.instance.instant('core.login.recaptchaincorrect');
}
CoreDomUtils.instance.showErrorModal(error);
} else {
CoreDomUtils.instance.showErrorModal('core.login.usernotaddederror', true);
}
}
} catch (error) {
CoreDomUtils.instance.showErrorModalDefault(error, 'core.login.usernotaddederror', true);
} finally {
modal.dismiss();
}
}
/**
* Escape mail to avoid special characters to be treated as a RegExp.
*
* @param text Initial mail.
* @return Escaped mail.
*/
escapeMail(text: string): string {
return CoreTextUtils.instance.escapeForRegex(text);
}
/**
* Show authentication instructions.
*/
protected showAuthInstructions(): void {
CoreTextUtils.instance.viewText(Translate.instance.instant('core.login.instructions'), this.authInstructions!);
}
/**
* Show contact information on site (we have to display again the age verification form).
*/
showContactOnSite(): void {
CoreUtils.instance.openInBrowser(CoreTextUtils.instance.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'));
}
/**
* Verify Age.
*
* @param e Event.
* @return Promise resolved when done.
*/
async verifyAge(e: Event): Promise<void> {
e.preventDefault();
e.stopPropagation();
if (!this.ageVerificationForm.valid) {
CoreDomUtils.instance.showErrorModal('core.errorinvalidform', true);
return;
}
const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
const params = this.ageVerificationForm.value;
params.age = parseInt(params.age, 10); // Use just the integer part.
try {
const result = await CoreWS.instance.callAjax<IsMinorResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl });
CoreDomUtils.instance.triggerFormSubmittedEvent(this.ageFormElement, true);
if (!result.status) {
if (this.countryControl.value) {
this.signUpCountryControl!.setValue(this.countryControl.value);
}
// Not a minor, go ahead.
this.ageDigitalConsentVerification = false;
} else {
// Is a minor.
this.isMinor = true;
}
} catch (error) {
// Something wrong, redirect to the site.
CoreDomUtils.instance.showErrorModal('There was an error verifying your age, please try again using the browser.');
} finally {
modal.dismiss();
}
}
}
/**
* Result of WS core_auth_is_age_digital_consent_verification_enabled.
*/
export type IsAgeVerificationEnabledResponse = {
status: boolean; // True if digital consent verification is enabled, false otherwise.
};
/**
* Result of WS auth_email_signup_user.
*/
export type SignupUserResult = {
success: boolean; // True if the user was created false otherwise.
warnings?: CoreWSExternalWarning[];
};
/**
* Result of WS core_auth_is_minor.
*/
export type IsMinorResult = {
status: boolean; // True if the user is considered to be a digital minor, false if not.
};

View File

@ -204,7 +204,7 @@ export class CoreLoginHelperProvider {
* @param profileFields Profile fields to format.
* @return Categories with the fields to show in each one.
*/
formatProfileFieldsForSignup(profileFields: AuthEmailSignupProfileField[]): AuthEmailSignupProfileFieldsCategory[] {
formatProfileFieldsForSignup(profileFields?: AuthEmailSignupProfileField[]): AuthEmailSignupProfileFieldsCategory[] {
if (!profileFields) {
return [];
}
@ -269,8 +269,8 @@ export class CoreLoginHelperProvider {
maxlengthMsg?: string,
minMsg?: string,
maxMsg?: string,
): any {
const errors: any = {};
): Record<string, string> {
const errors: Record<string, string> = {};
if (requiredMsg) {
errors.required = errors.requiredTrue = Translate.instance.instant(requiredMsg);

View File

@ -1014,7 +1014,7 @@ export class CoreDomUtilsProvider {
* @deprecated since 3.9.5. Use directly the IonContent class.
*/
scrollTo(content: IonContent, x: number, y: number, duration?: number): Promise<void> {
return content?.scrollByPoint(x, y, duration || 0);
return content?.scrollToPoint(x, y, duration || 0);
}
/**
@ -1104,7 +1104,7 @@ export class CoreDomUtilsProvider {
return false;
}
content?.scrollByPoint(position[0], position[1], duration || 0);
content?.scrollToPoint(position[0], position[1], duration || 0);
return true;
}
@ -1124,6 +1124,8 @@ export class CoreDomUtilsProvider {
scrollParentClass?: string,
duration?: number,
): Promise<boolean> {
// @todo: This function is broken. Scroll element cannot be used because it uses shadow DOM so querySelector returns null.
// Also, traversing using parentElement doesn't work either, offsetParent isn't part of the parentElement tree.
try {
const scrollElement = await content.getScrollElement();
@ -1132,7 +1134,7 @@ export class CoreDomUtilsProvider {
return false;
}
content?.scrollByPoint(position[0], position[1], duration || 0);
content?.scrollToPoint(position[0], position[1], duration || 0);
return true;
} catch (error) {
@ -1147,7 +1149,7 @@ export class CoreDomUtilsProvider {
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @return True if the element is found, false otherwise.
*/
async scrollToInputError(content: IonContent, scrollParentClass?: string): Promise<boolean> {
async scrollToInputError(content?: IonContent, scrollParentClass?: string): Promise<boolean> {
if (!content) {
return false;
}

View File

@ -618,7 +618,7 @@ export class CoreUtilsProvider {
*
* @return Promise resolved with the list of countries.
*/
getCountryListSorted(): Promise<{ code: string; name: string }[]> {
getCountryListSorted(): Promise<CoreCountry[]> {
// Get the keys of the countries.
return this.getCountryList().then((countries) => {
// Sort translations.
@ -1659,3 +1659,11 @@ export type OrderedPromiseData = {
*/
blocking?: boolean;
};
/**
* Data about a country.
*/
export type CoreCountry = {
code: string;
name: string;
};

View File

@ -90,3 +90,12 @@ ion-list.list-md {
visibility: hidden;
left: -1000px;
}
// Note on foot of ion-input.
.item .core-input-footnote {
width: 100%;
font-style: italic;
margin-top: 0;
margin-bottom: 10px;
font-size: 14px;
}