MOBILE-3565 login: Implement signup page
This commit is contained in:
parent
232669855a
commit
a6467f5073
@ -51,6 +51,10 @@ const routes: Routes = [
|
|||||||
path: 'sitepolicy',
|
path: 'sitepolicy',
|
||||||
loadChildren: () => import('./pages/site-policy/site-policy.module').then( m => m.CoreLoginSitePolicyPageModule),
|
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({
|
@NgModule({
|
||||||
|
@ -33,7 +33,10 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">
|
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">
|
||||||
<core-show-password [name]="'password'">
|
<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>
|
</core-show-password>
|
||||||
</ion-item>
|
</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>
|
<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-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-label><p><core-format-text [text]="authInstructions" [filter]="false"></core-format-text></p></ion-label>
|
||||||
</ion-item>
|
</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 }}
|
{{ 'core.login.startsignup' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-list>
|
</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.
|
* 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.
|
* @param profileFields Profile fields to format.
|
||||||
* @return Categories with the fields to show in each one.
|
* @return Categories with the fields to show in each one.
|
||||||
*/
|
*/
|
||||||
formatProfileFieldsForSignup(profileFields: AuthEmailSignupProfileField[]): AuthEmailSignupProfileFieldsCategory[] {
|
formatProfileFieldsForSignup(profileFields?: AuthEmailSignupProfileField[]): AuthEmailSignupProfileFieldsCategory[] {
|
||||||
if (!profileFields) {
|
if (!profileFields) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -269,8 +269,8 @@ export class CoreLoginHelperProvider {
|
|||||||
maxlengthMsg?: string,
|
maxlengthMsg?: string,
|
||||||
minMsg?: string,
|
minMsg?: string,
|
||||||
maxMsg?: string,
|
maxMsg?: string,
|
||||||
): any {
|
): Record<string, string> {
|
||||||
const errors: any = {};
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
if (requiredMsg) {
|
if (requiredMsg) {
|
||||||
errors.required = errors.requiredTrue = Translate.instance.instant(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.
|
* @deprecated since 3.9.5. Use directly the IonContent class.
|
||||||
*/
|
*/
|
||||||
scrollTo(content: IonContent, x: number, y: number, duration?: number): Promise<void> {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
content?.scrollByPoint(position[0], position[1], duration || 0);
|
content?.scrollToPoint(position[0], position[1], duration || 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1124,6 +1124,8 @@ export class CoreDomUtilsProvider {
|
|||||||
scrollParentClass?: string,
|
scrollParentClass?: string,
|
||||||
duration?: number,
|
duration?: number,
|
||||||
): Promise<boolean> {
|
): 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 {
|
try {
|
||||||
const scrollElement = await content.getScrollElement();
|
const scrollElement = await content.getScrollElement();
|
||||||
|
|
||||||
@ -1132,7 +1134,7 @@ export class CoreDomUtilsProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
content?.scrollByPoint(position[0], position[1], duration || 0);
|
content?.scrollToPoint(position[0], position[1], duration || 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1147,7 +1149,7 @@ export class CoreDomUtilsProvider {
|
|||||||
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
|
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
|
||||||
* @return True if the element is found, false otherwise.
|
* @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) {
|
if (!content) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -618,7 +618,7 @@ export class CoreUtilsProvider {
|
|||||||
*
|
*
|
||||||
* @return Promise resolved with the list of countries.
|
* @return Promise resolved with the list of countries.
|
||||||
*/
|
*/
|
||||||
getCountryListSorted(): Promise<{ code: string; name: string }[]> {
|
getCountryListSorted(): Promise<CoreCountry[]> {
|
||||||
// Get the keys of the countries.
|
// Get the keys of the countries.
|
||||||
return this.getCountryList().then((countries) => {
|
return this.getCountryList().then((countries) => {
|
||||||
// Sort translations.
|
// Sort translations.
|
||||||
@ -1659,3 +1659,11 @@ export type OrderedPromiseData = {
|
|||||||
*/
|
*/
|
||||||
blocking?: boolean;
|
blocking?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data about a country.
|
||||||
|
*/
|
||||||
|
export type CoreCountry = {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
@ -90,3 +90,12 @@ ion-list.list-md {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
left: -1000px;
|
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