MOBILE-3565 login: Implement signup page
This commit is contained in:
parent
232669855a
commit
a6467f5073
@ -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({
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
*/
|
||||
|
238
src/app/core/login/pages/email-signup/email-signup.html
Normal file
238
src/app/core/login/pages/email-signup/email-signup.html
Normal 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>
|
50
src/app/core/login/pages/email-signup/email-signup.module.ts
Normal file
50
src/app/core/login/pages/email-signup/email-signup.module.ts
Normal 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 {}
|
419
src/app/core/login/pages/email-signup/email-signup.page.ts
Normal file
419
src/app/core/login/pages/email-signup/email-signup.page.ts
Normal 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.
|
||||
};
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user