From c75f45f31e5c441c3fe3955ff11b47bf95d92dca Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Tue, 14 Nov 2023 09:04:25 -0600 Subject: [PATCH 01/21] Registration-names-par-1 --- src/app/app-routing.module.ts | 8 +- src/app/app.component.ts | 18 +- src/app/guards/register-toggl.guard.ts | 34 ++ .../institutional.component.scss-theme.scss | 2 +- src/app/layout/header/header.component.ts | 5 +- .../ErrorStateMatcherForFormLevelErrors.ts | 35 ++ src/app/register2/components/BaseForm.ts | 68 ++++ src/app/register2/components/BaseStep.ts | 16 + .../backend-error.component.html | 45 +++ .../backend-error.component.scss | 0 .../backend-error.component.spec.ts | 45 +++ .../backend-error/backend-error.component.ts | 100 ++++++ .../form-anti-robots.component.html | 14 + .../form-anti-robots.component.scss | 0 .../form-anti-robots.component.spec.ts | 47 +++ .../form-anti-robots.component.ts | 99 +++++ .../form-notifications.component.html | 35 ++ .../form-notifications.component.scss | 3 + .../form-notifications.component.spec.ts | 48 +++ .../form-notifications.component.ts | 51 +++ .../form-password.component.html | 193 ++++++++++ .../form-password.component.scss | 20 ++ .../form-password.component.scss-theme.scss | 16 + .../form-password.component.spec.ts | 52 +++ .../form-password/form-password.component.ts | 112 ++++++ ...-personal-additional-emails.component.html | 86 +++++ ...-personal-additional-emails.component.scss | 11 + ...rsonal-additional-emails.component.spec.ts | 26 ++ ...rm-personal-additional-emails.component.ts | 93 +++++ .../form-personal.component.html | 125 +++++++ .../form-personal.component.scss | 5 + .../form-personal.component.spec.ts | 54 +++ .../form-personal/form-personal.component.ts | 197 ++++++++++ .../form-terms/form-terms.component.html | 66 ++++ .../form-terms/form-terms.component.scss | 12 + .../form-terms/form-terms.component.spec.ts | 45 +++ .../form-terms/form-terms.component.ts | 69 ++++ .../form-visibility.component.html | 95 +++++ .../form-visibility.component.scss | 10 + .../form-visibility.component.spec.ts | 45 +++ .../form-visibility.component.ts | 68 ++++ .../components/register2.scss-theme.scss | 19 + .../register2/components/register2.style.scss | 113 ++++++ .../components/step-a/step-a.component.html | 78 ++++ .../components/step-a/step-a.component.scss | 6 + .../step-a/step-a.component.spec.ts | 43 +++ .../components/step-a/step-a.component.ts | 76 ++++ .../components/step-b/step-b.component.html | 60 ++++ .../components/step-b/step-b.component.scss | 4 + .../step-b/step-b.component.spec.ts | 24 ++ .../components/step-b/step-b.component.ts | 18 + .../components/step-c/step-c.component.html | 73 ++++ .../components/step-c/step-c.component.scss | 4 + .../step-c/step-c.component.spec.ts | 24 ++ .../components/step-c/step-c.component.ts | 18 + .../top-bar-record-issues.component.html | 1 + .../top-bar-record-issues.component.scss | 0 .../top-bar-record-issues.component.spec.ts | 24 ++ .../top-bar-record-issues.component.ts | 12 + .../pages/register/register.component.html | 51 +++ .../pages/register/register.component.scss | 15 + .../pages/register/register.component.spec.ts | 54 +++ .../pages/register/register.component.ts | 314 ++++++++++++++++ .../pages/register/register2.component.html | 94 +++++ .../pages/register/register2.component.scss | 9 + .../pages/register/register2.component.ts | 339 ++++++++++++++++++ src/app/register2/register-routing.module.ts | 16 + src/app/register2/register.module.ts | 74 ++++ 68 files changed, 3622 insertions(+), 14 deletions(-) create mode 100644 src/app/guards/register-toggl.guard.ts create mode 100644 src/app/register2/ErrorStateMatcherForFormLevelErrors.ts create mode 100644 src/app/register2/components/BaseForm.ts create mode 100644 src/app/register2/components/BaseStep.ts create mode 100644 src/app/register2/components/backend-error/backend-error.component.html create mode 100644 src/app/register2/components/backend-error/backend-error.component.scss create mode 100644 src/app/register2/components/backend-error/backend-error.component.spec.ts create mode 100644 src/app/register2/components/backend-error/backend-error.component.ts create mode 100644 src/app/register2/components/form-anti-robots/form-anti-robots.component.html create mode 100644 src/app/register2/components/form-anti-robots/form-anti-robots.component.scss create mode 100644 src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts create mode 100644 src/app/register2/components/form-anti-robots/form-anti-robots.component.ts create mode 100644 src/app/register2/components/form-notifications/form-notifications.component.html create mode 100644 src/app/register2/components/form-notifications/form-notifications.component.scss create mode 100644 src/app/register2/components/form-notifications/form-notifications.component.spec.ts create mode 100644 src/app/register2/components/form-notifications/form-notifications.component.ts create mode 100644 src/app/register2/components/form-password/form-password.component.html create mode 100644 src/app/register2/components/form-password/form-password.component.scss create mode 100644 src/app/register2/components/form-password/form-password.component.scss-theme.scss create mode 100644 src/app/register2/components/form-password/form-password.component.spec.ts create mode 100644 src/app/register2/components/form-password/form-password.component.ts create mode 100644 src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.html create mode 100644 src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.scss create mode 100644 src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.spec.ts create mode 100644 src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.ts create mode 100644 src/app/register2/components/form-personal/form-personal.component.html create mode 100644 src/app/register2/components/form-personal/form-personal.component.scss create mode 100644 src/app/register2/components/form-personal/form-personal.component.spec.ts create mode 100644 src/app/register2/components/form-personal/form-personal.component.ts create mode 100644 src/app/register2/components/form-terms/form-terms.component.html create mode 100644 src/app/register2/components/form-terms/form-terms.component.scss create mode 100644 src/app/register2/components/form-terms/form-terms.component.spec.ts create mode 100644 src/app/register2/components/form-terms/form-terms.component.ts create mode 100644 src/app/register2/components/form-visibility/form-visibility.component.html create mode 100644 src/app/register2/components/form-visibility/form-visibility.component.scss create mode 100644 src/app/register2/components/form-visibility/form-visibility.component.spec.ts create mode 100644 src/app/register2/components/form-visibility/form-visibility.component.ts create mode 100644 src/app/register2/components/register2.scss-theme.scss create mode 100644 src/app/register2/components/register2.style.scss create mode 100644 src/app/register2/components/step-a/step-a.component.html create mode 100644 src/app/register2/components/step-a/step-a.component.scss create mode 100644 src/app/register2/components/step-a/step-a.component.spec.ts create mode 100644 src/app/register2/components/step-a/step-a.component.ts create mode 100644 src/app/register2/components/step-b/step-b.component.html create mode 100644 src/app/register2/components/step-b/step-b.component.scss create mode 100644 src/app/register2/components/step-b/step-b.component.spec.ts create mode 100644 src/app/register2/components/step-b/step-b.component.ts create mode 100644 src/app/register2/components/step-c/step-c.component.html create mode 100644 src/app/register2/components/step-c/step-c.component.scss create mode 100644 src/app/register2/components/step-c/step-c.component.spec.ts create mode 100644 src/app/register2/components/step-c/step-c.component.ts create mode 100644 src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.html create mode 100644 src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.scss create mode 100644 src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.spec.ts create mode 100644 src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.ts create mode 100644 src/app/register2/pages/register/register.component.html create mode 100644 src/app/register2/pages/register/register.component.scss create mode 100644 src/app/register2/pages/register/register.component.spec.ts create mode 100644 src/app/register2/pages/register/register.component.ts create mode 100644 src/app/register2/pages/register/register2.component.html create mode 100644 src/app/register2/pages/register/register2.component.scss create mode 100644 src/app/register2/pages/register/register2.component.ts create mode 100644 src/app/register2/register-routing.module.ts create mode 100644 src/app/register2/register.module.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index de67975194..3d0573fbd9 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' +import { LoadChildrenCallback, RouterModule, Routes } from '@angular/router' import { ApplicationRoutes, @@ -91,7 +91,9 @@ const routes: Routes = [ path: ApplicationRoutes.register, canActivateChild: [LanguageGuard, RegisterGuard], loadChildren: () => - import('./register/register.module').then((m) => m.RegisterModule), + (!localStorage.getItem('register2') && + import('./register/register.module').then((m) => m.RegisterModule)) || + import('./register2/register.module').then((m) => m.Register2Module), }, { path: ApplicationRoutes.search, @@ -179,4 +181,4 @@ const routes: Routes = [ imports: [RouterModule.forRoot(routes, {})], exports: [RouterModule], }) -export class AppRoutingModule {} +export class AppRoutingModule { } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0e02e598b8..47cfd0477e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,21 +2,20 @@ import { Component, HostBinding, HostListener, Inject } from '@angular/core' import { NavigationEnd, NavigationStart, Router } from '@angular/router' import { catchError, tap } from 'rxjs/operators' +import { + finishPerformanceMeasurement, + reportNavigationStart, +} from './analytics-utils' import { PlatformInfo } from './cdk/platform-info' import { PlatformInfoService } from './cdk/platform-info/platform-info.service' import { WINDOW } from './cdk/window' import { HeadlessOnOauthRoutes } from './constants' import { UserService } from './core' -import { ZendeskService } from './core/zendesk/zendesk.service' -import { GoogleTagManagerService } from './core/google-tag-manager/google-tag-manager.service' -import { - finishPerformanceMeasurement, - reportNavigationStart, -} from './analytics-utils' -import { ERROR_REPORT } from './errors' import { ErrorHandlerService } from './core/error-handler/error-handler.service' -import { environment } from 'src/environments/environment' +import { GoogleTagManagerService } from './core/google-tag-manager/google-tag-manager.service' import { TitleService } from './core/title-service/title.service' +import { ZendeskService } from './core/zendesk/zendesk.service' +import { ERROR_REPORT } from './errors' @Component({ selector: 'app-root', @@ -59,6 +58,7 @@ export class AppComponent { this.currentRouteIsHeadlessOnOauthPage = this.showHeadlessOnOauthPage( platformInfo.currentRoute ) + console.log('currentRouteIsHeadlessOnOauthPage', this.currentRouteIsHeadlessOnOauthPage) this.setPlatformClasses(platformInfo) this.screenDirection = platformInfo.screenDirection if ( @@ -113,6 +113,8 @@ export class AppComponent { } showHeadlessOnOauthPage(currentRoute: string): boolean { if (currentRoute) { + console.log(currentRoute) + const value = HeadlessOnOauthRoutes.filter( (url) => currentRoute.indexOf('/' + url) === 0 ) diff --git a/src/app/guards/register-toggl.guard.ts b/src/app/guards/register-toggl.guard.ts new file mode 100644 index 0000000000..d77b600899 --- /dev/null +++ b/src/app/guards/register-toggl.guard.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core' +import { + ActivatedRouteSnapshot, + Router, + RouterStateSnapshot, + UrlTree, +} from '@angular/router' +import { combineLatest, Observable } from 'rxjs' +import { map } from 'rxjs/operators' + +import { ApplicationRoutes } from '../constants' +import { UserService } from '../core' +import { TogglzService } from '../core/togglz/togglz.service' + +@Injectable({ + providedIn: 'root', +}) +export class RegisterTogglGuard { + constructor( + private _userInfo: UserService, + private _router: Router, + private _togglz: TogglzService + ) { } + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): + | Observable + | Promise + | boolean + | UrlTree { + return false + } +} diff --git a/src/app/institutional/pages/institutional/institutional.component.scss-theme.scss b/src/app/institutional/pages/institutional/institutional.component.scss-theme.scss index 0639da0481..663cb13ef6 100644 --- a/src/app/institutional/pages/institutional/institutional.component.scss-theme.scss +++ b/src/app/institutional/pages/institutional/institutional.component.scss-theme.scss @@ -1,4 +1,4 @@ -@import '../../../../../node_modules/@angular/material/theming'; +@import '~@angular/material/theming'; @import '../../../../assets/scss/material.orcid-theme'; @mixin institutional-theme($theme) { diff --git a/src/app/layout/header/header.component.ts b/src/app/layout/header/header.component.ts index f7afd092af..6284f6d11e 100644 --- a/src/app/layout/header/header.component.ts +++ b/src/app/layout/header/header.component.ts @@ -5,7 +5,6 @@ import { filter } from 'rxjs/operators' import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' import { WINDOW } from 'src/app/cdk/window' import { UserService } from 'src/app/core' -import { SignInService } from 'src/app/core/sign-in/sign-in.service' import { TogglzService } from 'src/app/core/togglz/togglz.service' import { ApplicationMenuItem, UserInfo } from 'src/app/types' import { @@ -77,7 +76,9 @@ export class HeaderComponent implements OnInit { path === `/${ApplicationRoutes.trustedParties}` || path === `/${ApplicationRoutes.selfService}` || path === `/${ApplicationRoutes.inbox}` || - path === `/${ApplicationRoutes.developerTools}` + path === `/${ApplicationRoutes.developerTools}` || + path === `/${ApplicationRoutes.register}` + }) } diff --git a/src/app/register2/ErrorStateMatcherForFormLevelErrors.ts b/src/app/register2/ErrorStateMatcherForFormLevelErrors.ts new file mode 100644 index 0000000000..3877e5c3db --- /dev/null +++ b/src/app/register2/ErrorStateMatcherForFormLevelErrors.ts @@ -0,0 +1,35 @@ +import { UntypedFormControl, FormGroupDirective, NgForm } from '@angular/forms' +import { ErrorStateMatcher } from '@angular/material/core' + +export class ErrorStateMatcherForFormLevelErrors implements ErrorStateMatcher { + getControlErrorAtForm: ( + control: UntypedFormControl, + errorGroup: string + ) => string[] + errorGroup: string + constructor( + getControlErrorAtForm: ( + control: UntypedFormControl, + errorGroup: string + ) => string[], + errorGroup: string + ) { + this.getControlErrorAtForm = getControlErrorAtForm + this.errorGroup = errorGroup + } + isErrorState( + control: UntypedFormControl | null, + form: FormGroupDirective | NgForm | null + ): boolean { + const errorsAtFormLevel = this.getControlErrorAtForm( + control, + this.errorGroup + ) + const controlInteracted = control.touched || (form && form.submitted) + const validControlAtFormLevel = !( + errorsAtFormLevel && errorsAtFormLevel.length > 0 + ) + const validControl = control && !control.invalid + return !(validControlAtFormLevel && validControl) && controlInteracted + } +} diff --git a/src/app/register2/components/BaseForm.ts b/src/app/register2/components/BaseForm.ts new file mode 100644 index 0000000000..c599d948b8 --- /dev/null +++ b/src/app/register2/components/BaseForm.ts @@ -0,0 +1,68 @@ +import { + AbstractControl, + AsyncValidator, + ControlValueAccessor, + UntypedFormGroup, + ValidationErrors, +} from '@angular/forms' +import { merge, Observable, timer } from 'rxjs' +import { filter, map, startWith, take } from 'rxjs/operators' + +export abstract class BaseForm implements ControlValueAccessor, AsyncValidator { + public form: UntypedFormGroup + public onTouchedFunction + constructor() {} + writeValue(val: any): void { + console.log('val', val) + if (val != null && val !== undefined && val !== '') { + console.log('val go', val) + + this.form.setValue(val, { emitEvent: true }) + // Trigger registerOnChange custom function by calling form.updateValueAndValidity + // require since most form controls extending this class + // need to call the xxxxRegisterForm functions to adapt the original angular form value for the backend format + setTimeout(() => { + this.form.updateValueAndValidity() + }) + } + } + registerOnChange(fn: any): void { + this.form.valueChanges.subscribe((value) => { + fn(value) + }) + } + registerOnTouched(fn: any): void { + this.onTouchedFunction = fn + } + setDisabledState?(isDisabled: boolean): void { + isDisabled ? this.form.disable() : this.form.enable() + } + validate(c: AbstractControl): Observable { + // temporal fix + // see related issue + // https://github.com/angular/angular/issues/14542 + // depending of fix + // https://github.com/angular/angular/pull/20806 + // + // using form.statusChanges observable only would be a better solution for this scenario (see the code before this fix) + // but if the form status starts as `pending` Angular wont report the status change because of #14542 + // and the status might now start as `pending` with the introduction of Oauth registration + + return merge(this.form.statusChanges, timer(0, 1000)).pipe( + map(() => this.form.status), + startWith(this.form.status), + filter((value) => value !== 'PENDING'), + take(1), + map(() => { + return this.form.valid + ? null + : { + invalidForm: { + valid: false, + message: 'internal form is not valid', + }, + } + }) + ) + } +} diff --git a/src/app/register2/components/BaseStep.ts b/src/app/register2/components/BaseStep.ts new file mode 100644 index 0000000000..fdb03d461a --- /dev/null +++ b/src/app/register2/components/BaseStep.ts @@ -0,0 +1,16 @@ +import { EventEmitter, Input, Output, Directive } from '@angular/core' +import { UntypedFormGroup } from '@angular/forms' + +@Directive() +export abstract class BaseStepDirective { + public _formGroup: UntypedFormGroup + @Input() + set formGroup(formGroup: UntypedFormGroup) { + this._formGroup = formGroup + this.formGroupChange.emit(this._formGroup) + } + get formGroup() { + return this._formGroup + } + @Output() formGroupChange = new EventEmitter() +} diff --git a/src/app/register2/components/backend-error/backend-error.component.html b/src/app/register2/components/backend-error/backend-error.component.html new file mode 100644 index 0000000000..d08320c6bb --- /dev/null +++ b/src/app/register2/components/backend-error/backend-error.component.html @@ -0,0 +1,45 @@ + + This email already exists in our system. Would you like to + + sign in? + + + + Additional email cannot match primary email + + + + Additional emails cannot be duplicated + + + + The ORCID record exists but has not been claimed. Would you like + to + + resend the claim email? + + + + + A deactivated ORCID record is associated with this email address. + + click here to reactivate + + + {{ errorCode }} diff --git a/src/app/register2/components/backend-error/backend-error.component.scss b/src/app/register2/components/backend-error/backend-error.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/register2/components/backend-error/backend-error.component.spec.ts b/src/app/register2/components/backend-error/backend-error.component.spec.ts new file mode 100644 index 0000000000..444341d5e4 --- /dev/null +++ b/src/app/register2/components/backend-error/backend-error.component.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { BackendErrorComponent } from './backend-error.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { SignInService } from '../../../core/sign-in/sign-in.service' + +describe('BackendErrorComponent', () => { + let component: BackendErrorComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [BackendErrorComponent], + providers: [ + WINDOW_PROVIDERS, + SignInService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(BackendErrorComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/backend-error/backend-error.component.ts b/src/app/register2/components/backend-error/backend-error.component.ts new file mode 100644 index 0000000000..6e36e49331 --- /dev/null +++ b/src/app/register2/components/backend-error/backend-error.component.ts @@ -0,0 +1,100 @@ +import { Component, Input, OnInit, Inject } from '@angular/core' +import { Router } from '@angular/router' +import { take } from 'rxjs/operators' +import { PlatformInfoService } from 'src/app/cdk/platform-info' +import { SnackbarService } from 'src/app/cdk/snackbar/snackbar.service' +import { ApplicationRoutes } from 'src/app/constants' +import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' +import { SignInService } from 'src/app/core/sign-in/sign-in.service' +import { ERROR_REPORT } from 'src/app/errors' +import { WINDOW } from 'src/app/cdk/window' + +// When the error text is not listed on the RegisterBackendErrors enum +// the error message will be displayed as it comes from the backend +// This is because the backend might return code or a text ready for the UI +enum RegisterBackendErrors { + 'orcid.frontend.verify.duplicate_email', + 'additionalEmailCantBePrimaryEmail', + 'duplicatedAdditionalEmail', + 'orcid.frontend.verify.unclaimed_email', + 'orcid.frontend.verify.deactivated_email', +} + +@Component({ + selector: 'app-backend-error', + templateUrl: './backend-error.component.html', + styleUrls: ['./backend-error.component.scss'], + preserveWhitespaces: true, +}) +export class BackendErrorComponent implements OnInit { + recognizedError = RegisterBackendErrors + _errorCode: string + @Input() + set errorCode(errorCode: string) { + // This will change the string send by the backend into a code, to handle the error trough a code + if (errorCode.indexOf('resend-claim') >= 0) { + errorCode = RegisterBackendErrors[3] + } + this._errorCode = errorCode + } + get errorCode() { + return this._errorCode + } + @Input() value?: string + unrecognizedError = false + constructor( + private _platformInfo: PlatformInfoService, + private _router: Router, + private _snackbar: SnackbarService, + private _signIn: SignInService, + private _errorHandler: ErrorHandlerService, + @Inject(WINDOW) private window: Window + ) {} + ngOnInit() { + if (!(this.errorCode in RegisterBackendErrors)) { + this.unrecognizedError = true + } + } + + navigateToClaim(email) { + email = encodeURIComponent(email) + this.window.location.href = `/resend-claim?email=${email}` + } + + navigateToSignin(email) { + this._platformInfo + .get() + .pipe(take(1)) + .subscribe((platform) => { + return this._router.navigate([ApplicationRoutes.signin], { + // keeps all parameters to support Oauth request + // and set show login to true + queryParams: { ...platform.queryParameters, email, show_login: true }, + }) + }) + } + + reactivateEmail(email) { + const $deactivate = this._signIn.reactivation(email) + $deactivate.subscribe((data) => { + if (data.error) { + this._errorHandler + .handleError( + new Error(data.error), + ERROR_REPORT.REGISTER_REACTIVATED_EMAIL + ) + .subscribe() + } else { + this._snackbar.showSuccessMessage({ + title: $localize`:@@register.reactivating:Reactivating your account`, + // tslint:disable-next-line: max-line-length + message: $localize`:@@ngOrcid.signin.verify.reactivationSent:Thank you for reactivating your ORCID record; please complete the process by following the steps in the email we are now sending you. If you don’t receive an email from us, please`, + action: $localize`:@@shared.contactSupport:contact support.`, + actionURL: `https://support.orcid.org/`, + closable: true, + }) + this._router.navigate([ApplicationRoutes.signin]) + } + }) + } +} diff --git a/src/app/register2/components/form-anti-robots/form-anti-robots.component.html b/src/app/register2/components/form-anti-robots/form-anti-robots.component.html new file mode 100644 index 0000000000..20bd4df427 --- /dev/null +++ b/src/app/register2/components/form-anti-robots/form-anti-robots.component.html @@ -0,0 +1,14 @@ + +
+ + Please check the recaptcha box +
diff --git a/src/app/register2/components/form-anti-robots/form-anti-robots.component.scss b/src/app/register2/components/form-anti-robots/form-anti-robots.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts b/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts new file mode 100644 index 0000000000..eb5403a67e --- /dev/null +++ b/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts @@ -0,0 +1,47 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormAntiRobotsComponent } from './form-anti-robots.component' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { RegisterService } from '../../../core/register/register.service' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { Overlay } from '@angular/cdk/overlay' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { ErrorStateMatcher } from '@angular/material/core' + +describe('FormAntiRobotsComponent', () => { + let component: FormAntiRobotsComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [FormAntiRobotsComponent], + providers: [ + WINDOW_PROVIDERS, + RegisterService, + ErrorStateMatcher, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormAntiRobotsComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts b/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts new file mode 100644 index 0000000000..959ba4876b --- /dev/null +++ b/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts @@ -0,0 +1,99 @@ +import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' +import { + UntypedFormControl, + UntypedFormGroup, + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidatorFn, + Validators, +} from '@angular/forms' +import { ErrorStateMatcher } from '@angular/material/core' +import { merge, Subject } from 'rxjs' +import { RegisterService } from 'src/app/core/register/register.service' + +import { BaseForm } from '../BaseForm' + +@Component({ + selector: 'app-form-anti-robots', + templateUrl: './form-anti-robots.component.html', + styleUrls: ['./form-anti-robots.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormAntiRobotsComponent), + multi: true, + }, + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => FormAntiRobotsComponent), + multi: true, + }, + ], +}) +export class FormAntiRobotsComponent + extends BaseForm + implements OnInit, DoCheck +{ + captchaFailState = false + captchaLoadedWithWidgetId: number + $widgetIdUpdated = new Subject() + errorState = false + captcha = new UntypedFormControl(null, { + validators: [this.captchaValidator()], + }) + ngOnInit(): void { + this.form = new UntypedFormGroup({ + captcha: this.captcha, + }) + } + + constructor( + private _register: RegisterService, + private _errorStateMatcher: ErrorStateMatcher + ) { + super() + } + + // Captcha must be clicked unless it was not loaded + captchaValidator(): ValidatorFn { + return (control: UntypedFormControl) => { + const hasError = Validators.required(control) + if ( + hasError && + hasError.required && + this.captchaLoadedWithWidgetId !== undefined + ) { + return { captcha: true } + } else { + return null + } + } + } + + captchaFail($event) { + this.captchaFailState = $event + } + captchaLoaded(widgetId: number) { + this.captchaLoadedWithWidgetId = widgetId + this.captcha.updateValueAndValidity() + this.$widgetIdUpdated.next() + } + + ngDoCheck(): void { + this.errorState = this._errorStateMatcher.isErrorState(this.captcha, null) + } + + // OVERWRITE + registerOnChange(fn: any) { + merge( + this.$widgetIdUpdated.asObservable(), + this.form.valueChanges + ).subscribe(() => { + const registerForm = this._register.formGroupToRecaptchaForm( + this.form, + this.captchaLoadedWithWidgetId + ) + fn(registerForm) + }) + } +} diff --git a/src/app/register2/components/form-notifications/form-notifications.component.html b/src/app/register2/components/form-notifications/form-notifications.component.html new file mode 100644 index 0000000000..d518331c03 --- /dev/null +++ b/src/app/register2/components/form-notifications/form-notifications.component.html @@ -0,0 +1,35 @@ +

+ Notification settings +

+ +

+ ORCID sends email notifications about items related to your account, security, + and privacy, including requests from ORCID member organizations for permission + to update your record, and changes made to your record by those organizations. +

+ +

+ You can also choose to receive emails from us about new features and tips for + making the most of your ORCID record. +

+ + + Please send me quarterly emails about new ORCID features and + tips. + To receive these emails, you will also need to verify your primary email + address. + + + +

+ After you've registered, you can change your notification settings at any time + in the account settings section of your ORCID record. +

diff --git a/src/app/register2/components/form-notifications/form-notifications.component.scss b/src/app/register2/components/form-notifications/form-notifications.component.scss new file mode 100644 index 0000000000..58207ec0a4 --- /dev/null +++ b/src/app/register2/components/form-notifications/form-notifications.component.scss @@ -0,0 +1,3 @@ +:host { + max-width: 100%; +} diff --git a/src/app/register2/components/form-notifications/form-notifications.component.spec.ts b/src/app/register2/components/form-notifications/form-notifications.component.spec.ts new file mode 100644 index 0000000000..74d5498538 --- /dev/null +++ b/src/app/register2/components/form-notifications/form-notifications.component.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormNotificationsComponent } from './form-notifications.component' +import { RegisterService } from '../../../core/register/register.service' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { + MatLegacyDialog as MatDialog, + MatLegacyDialogModule as MatDialogModule, +} from '@angular/material/legacy-dialog' +import { RouterTestingModule } from '@angular/router/testing' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { WINDOW_PROVIDERS } from '../../../cdk/window' + +describe('FormNotificationsComponent', () => { + let component: FormNotificationsComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [FormNotificationsComponent], + providers: [ + WINDOW_PROVIDERS, + RegisterService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormNotificationsComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-notifications/form-notifications.component.ts b/src/app/register2/components/form-notifications/form-notifications.component.ts new file mode 100644 index 0000000000..11982529df --- /dev/null +++ b/src/app/register2/components/form-notifications/form-notifications.component.ts @@ -0,0 +1,51 @@ +import { Component, forwardRef, OnInit } from '@angular/core' +import { + UntypedFormControl, + UntypedFormGroup, + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + Validators, +} from '@angular/forms' +import { RegisterService } from 'src/app/core/register/register.service' + +import { BaseForm } from '../BaseForm' + +@Component({ + selector: 'app-form-notifications', + templateUrl: './form-notifications.component.html', + styleUrls: ['./form-notifications.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormNotificationsComponent), + multi: true, + }, + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => FormNotificationsComponent), + multi: true, + }, + ], +}) +export class FormNotificationsComponent extends BaseForm implements OnInit { + constructor(private _register: RegisterService) { + super() + } + ngOnInit() { + this.form = new UntypedFormGroup({ + sendOrcidNews: new UntypedFormControl(false, { + validators: Validators.required, + }), + }) + } + + // OVERWRITE + registerOnChange(fn: any) { + this.form.valueChanges.subscribe((value) => { + const registerForm = this._register.formGroupToSendOrcidNewsForm( + this.form as UntypedFormGroup + ) + fn(registerForm) + }) + } +} diff --git a/src/app/register2/components/form-password/form-password.component.html b/src/app/register2/components/form-password/form-password.component.html new file mode 100644 index 0000000000..7c8077b9be --- /dev/null +++ b/src/app/register2/components/form-password/form-password.component.html @@ -0,0 +1,193 @@ +
+
+ + Password + + + A password is required + + + Password must not be the same as your email address + + + Password must match all pattern requirements + + + Password must be between 8 and 256 characters + + +
+ +
+
+
+ + +
+ +
    +
  1. + +
    8 or more characters
    +
  2. +
  3. + +
    1 letter or symbol
    +
  4. +
  5. + +
    1 number
    +
  6. +
+ + + Confirm password + + + Retype your password + + + Password must not be the same as your email address + + + + The password and confirmed password must match + + +
+ + + check_circle + + + + check_circle + + + + +
+

+ Must be between 8 and 256 characters long and contain: +

+
    +
  • at least 1 numeral: 0 - 9
  • +
  • + at least 1 of the following: + +
      +
    • + alpha character, case-sensitive a-Z +
    • +
    • + + any of the following symbols:
      + ! @ # $ % ^ * ( ) ~ ` {{ '{ }' }} [ ] | \ & _ +
    • +
    +
  • +
  • + optionally the space character, +
    + i.e ' ' and other punctuation such as . , ; +
  • +
+

+ Example: sun% moon2 +

+

+ + ORCID does not allow common passwords. Common passwords are insecure, + easily-guessed words or phrases such as 'qwerty123'. + + See the full list of common passwords we don't allow on ORCID +

+
+
+
diff --git a/src/app/register2/components/form-password/form-password.component.scss b/src/app/register2/components/form-password/form-password.component.scss new file mode 100644 index 0000000000..5814f8f6f4 --- /dev/null +++ b/src/app/register2/components/form-password/form-password.component.scss @@ -0,0 +1,20 @@ +:host { + display: flex; + flex-direction: column; +} + +ol { + margin-block-start: 0px; + padding-inline-start: 0px; + padding: 0; + li { + list-style-type: none; + display: flex; + img { + margin-inline-end: 4px; + } + div { + margin: 2px; + } + } +} diff --git a/src/app/register2/components/form-password/form-password.component.scss-theme.scss b/src/app/register2/components/form-password/form-password.component.scss-theme.scss new file mode 100644 index 0000000000..85ff72cd1f --- /dev/null +++ b/src/app/register2/components/form-password/form-password.component.scss-theme.scss @@ -0,0 +1,16 @@ +@use '@angular/material' as mat; +@import 'src/assets/scss/material.orcid-theme.scss'; + +@mixin form-password($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, accent); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + ::ng-deep .valid { + color: mat.get-color-from-palette($accent, 900); + } +} + +@include form-password($orcid-app-theme); diff --git a/src/app/register2/components/form-password/form-password.component.spec.ts b/src/app/register2/components/form-password/form-password.component.spec.ts new file mode 100644 index 0000000000..32f79f6159 --- /dev/null +++ b/src/app/register2/components/form-password/form-password.component.spec.ts @@ -0,0 +1,52 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormPasswordComponent } from './form-password.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { + MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, + MatLegacyDialog as MatDialog, + MatLegacyDialogRef as MatDialogRef, +} from '@angular/material/legacy-dialog' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { RegisterService } from '../../../core/register/register.service' +import { MdePopoverModule } from '../../../cdk/popover' + +describe('FormPasswordComponent', () => { + let component: FormPasswordComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MdePopoverModule, RouterTestingModule], + declarations: [FormPasswordComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + WINDOW_PROVIDERS, + RegisterService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormPasswordComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-password/form-password.component.ts b/src/app/register2/components/form-password/form-password.component.ts new file mode 100644 index 0000000000..b24e5b3e56 --- /dev/null +++ b/src/app/register2/components/form-password/form-password.component.ts @@ -0,0 +1,112 @@ +import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core' +import { + UntypedFormControl, + UntypedFormGroup, + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidatorFn, + Validators, +} from '@angular/forms' +import { HAS_LETTER_OR_SYMBOL, HAS_NUMBER } from 'src/app/constants' +import { RegisterService } from 'src/app/core/register/register.service' +import { RegisterForm } from 'src/app/types/register.endpoint' +import { OrcidValidators } from 'src/app/validators' + +import { BaseForm } from '../BaseForm' + +@Component({ + selector: 'app-form-password', + templateUrl: './form-password.component.html', + styleUrls: [ + './form-password.component.scss-theme.scss', + './form-password.component.scss', + ], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormPasswordComponent), + multi: true, + }, + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => FormPasswordComponent), + multi: true, + }, + ], + preserveWhitespaces: true, +}) +export class FormPasswordComponent extends BaseForm implements OnInit { + labelInfo = $localize`:@@register.ariaLabelInfoPassword:info about password` + labelClose = $localize`:@@register.ariaLabelClose:close` + @ViewChild(`#passwordPopover`) passwordPopover + @ViewChild(`#passwordPopoverTrigger`) passwordPopoverTrigger + hasNumberPattern = HAS_NUMBER + hasLetterOrSymbolPattern = HAS_LETTER_OR_SYMBOL + @Input() personalData: RegisterForm + constructor(private _register: RegisterService) { + super() + } + ngOnInit() { + this.form = new UntypedFormGroup( + { + password: new UntypedFormControl('', { + validators: [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(256), + Validators.pattern(this.hasNumberPattern), + Validators.pattern(this.hasLetterOrSymbolPattern), + this.passwordDoesNotContainUserEmails(), + ], + asyncValidators: [this._register.backendValueValidate('password')], + }), + passwordConfirm: new UntypedFormControl('', Validators.required), + }, + { + validators: OrcidValidators.matchValues('password', 'passwordConfirm'), + asyncValidators: this._register.backendPasswordValidate(), + } + ) + } + + passwordDoesNotContainUserEmails(): ValidatorFn { + return (control: UntypedFormControl) => { + const password: string = control.value + let hasError = false + + if (this.personalData && password) { + Object.keys(this.personalData.emailsAdditional).forEach((key) => { + const additionalEmail = this.personalData.emailsAdditional[key].value + if (password.indexOf(additionalEmail) >= 0) { + hasError = true + } + }) + } + + if ( + this.personalData && + this.personalData.email && + password.indexOf(this.personalData.email.value) >= 0 + ) { + hasError = true + } + + if (hasError) { + return { passwordIsEqualToTheEmail: true } + } else { + return null + } + } + } + + // OVERWRITE + registerOnChange(fn: any) { + this.form.valueChanges.subscribe((value) => { + const registerForm = this._register.formGroupToPasswordRegisterForm( + this.form as UntypedFormGroup + ) + + fn(registerForm) + }) + } +} diff --git a/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.html b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.html new file mode 100644 index 0000000000..55e5caded6 --- /dev/null +++ b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.html @@ -0,0 +1,86 @@ + +
+ + + Additional email + {{ i !== 0 ? i : '' }} + + + + + + + + + Invalid email format + + + + + + + +
+
+ + + diff --git a/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.scss b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.scss new file mode 100644 index 0000000000..c0ebe826ac --- /dev/null +++ b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.scss @@ -0,0 +1,11 @@ +:host { + display: flex; + flex-direction: column; +} + +a { + display: flex; + span { + margin: 2px; + } +} diff --git a/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.spec.ts b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.spec.ts new file mode 100644 index 0000000000..f9b8f371b4 --- /dev/null +++ b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.spec.ts @@ -0,0 +1,26 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormPersonalAdditionalEmailsComponent } from './form-personal-additional-emails.component' +import { MdePopoverModule } from '../../../cdk/popover' + +describe('FormPersonalAdditionalEmailsComponent', () => { + let component: FormPersonalAdditionalEmailsComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [MdePopoverModule], + declarations: [FormPersonalAdditionalEmailsComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormPersonalAdditionalEmailsComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.ts b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.ts new file mode 100644 index 0000000000..81f615e6dd --- /dev/null +++ b/src/app/register2/components/form-personal-additional-emails/form-personal-additional-emails.component.ts @@ -0,0 +1,93 @@ +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Input, + QueryList, + ViewChildren, +} from '@angular/core' +import { + AbstractControl, + UntypedFormControl, + UntypedFormGroup, +} from '@angular/forms' +import { OrcidValidators } from 'src/app/validators' + +import { ErrorStateMatcherForFormLevelErrors } from '../../ErrorStateMatcherForFormLevelErrors' + +@Component({ + selector: 'app-form-personal-additional-emails', + templateUrl: './form-personal-additional-emails.component.html', + styleUrls: ['./form-personal-additional-emails.component.scss', '../register2.style.scss'], +}) +export class FormPersonalAdditionalEmailsComponent implements AfterViewInit { + labelInfoAboutEmails = $localize`:@@register.ariaLabelInfoEmails:info about emails` + labelDeleteEmail = $localize`:@@register.ariaLabelDeleteEmail:delete email` + labelClose = $localize`:@@register.ariaLabelClose:close` + labelAddAnAddionalEmail = $localize`:@@register.addAnAdditionalEmail:Add an additional email` + @ViewChildren('emailInput') inputs: QueryList + @Input() additionalEmails: UntypedFormGroup + additionalEmailsPopoverTrigger + additionalEmailsCount = 1 + + constructor( + private _ref: ChangeDetectorRef, + private _changeDetectorRef: ChangeDetectorRef + ) { } + + backendErrorsMatcher = new ErrorStateMatcherForFormLevelErrors( + this.getControlErrorAtFormLevel, + 'backendErrors' + ) + + getControlErrorAtFormLevel( + control: AbstractControl | null, + errorGroup: string + ): string[] { + return ( + control?.parent?.parent?.errors?.[errorGroup]?.['additionalEmails'][ + control.value + ] || [] + ) + } + + // deleteEmailInput(id: string): void { + // this.additionalEmails.removeControl(id) + // this._changeDetectorRef.detectChanges() + + // const input = this.inputs.filter( + // (x) => this.parseInt(x.nativeElement.id) > this.parseInt(id) + // )?.[0] + // if (input) { + // input.nativeElement.focus() + // } else if (this.inputs.last) { + // this.inputs.last.nativeElement.focus() + // } + // } + + // addAdditionalEmail(): void { + // const controlName = ++this.additionalEmailsCount + // this.additionalEmails.addControl( + // this.zeroPad(controlName, 2), + // new UntypedFormControl('', { + // validators: [OrcidValidators.email], + // }) + // ) + // this._changeDetectorRef.detectChanges() + // const input = this.inputs.last.nativeElement as HTMLInputElement + // input.focus() + // } + + parseInt(number: string) { + return parseInt(number, 10) + } + + zeroPad(num, places) { + return String(num).padStart(places, '0') + } + + public ngAfterViewInit() { + this._ref.detectChanges() + } +} diff --git a/src/app/register2/components/form-personal/form-personal.component.html b/src/app/register2/components/form-personal/form-personal.component.html new file mode 100644 index 0000000000..34153e91b4 --- /dev/null +++ b/src/app/register2/components/form-personal/form-personal.component.html @@ -0,0 +1,125 @@ + +

Your names

+ + +
+ Given names + + + + + + Please enter your first/given name + + + Invalid name characters or format + + +
+ +
+
+
+
+ + Family names + + + + + + Invalid name characters or format + + +
+ +
+
+
+
+ +

Your email addresses

+ +
+ + Email + + + + + + An email is required + + + Invalid email format + + + +
+ +
+
+
+ + +
+ + + + + + + + Please confirm your email + + + Invalid email format + + + Email confirmation does not match + + +
+ +
+
+
+ + + +
+
+ + + +

+ First name is your given name or the name you most commonly go by. +

+

Last name is your family name.

+

+ You will have a chance to add additional names after you have created your + account. +

+ More information on + names +
+
\ No newline at end of file diff --git a/src/app/register2/components/form-personal/form-personal.component.scss b/src/app/register2/components/form-personal/form-personal.component.scss new file mode 100644 index 0000000000..fde0f78e1f --- /dev/null +++ b/src/app/register2/components/form-personal/form-personal.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; +} + diff --git a/src/app/register2/components/form-personal/form-personal.component.spec.ts b/src/app/register2/components/form-personal/form-personal.component.spec.ts new file mode 100644 index 0000000000..b34b3e95fe --- /dev/null +++ b/src/app/register2/components/form-personal/form-personal.component.spec.ts @@ -0,0 +1,54 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormPersonalComponent } from './form-personal.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { + MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, + MatLegacyDialog as MatDialog, + MatLegacyDialogRef as MatDialogRef, +} from '@angular/material/legacy-dialog' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { FormBuilder } from '@angular/forms' +import { RecordWorksService } from '../../../core/record-works/record-works.service' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { RegisterService } from '../../../core/register/register.service' +import { ReactivationService } from '../../../core/reactivation/reactivation.service' +import { MdePopoverModule } from '../../../cdk/popover' + +describe('FormPersonalComponent', () => { + let component: FormPersonalComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MdePopoverModule, RouterTestingModule], + declarations: [FormPersonalComponent], + providers: [ + WINDOW_PROVIDERS, + ReactivationService, + RegisterService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormPersonalComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-personal/form-personal.component.ts b/src/app/register2/components/form-personal/form-personal.component.ts new file mode 100644 index 0000000000..d1b5a8839c --- /dev/null +++ b/src/app/register2/components/form-personal/form-personal.component.ts @@ -0,0 +1,197 @@ +import { + AfterViewInit, + Component, + ElementRef, + forwardRef, + Input, + OnInit, + ViewChild, +} from '@angular/core' +import { + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, + ValidatorFn, + Validators, +} from '@angular/forms' +import { RegisterService } from 'src/app/core/register/register.service' +import { OrcidValidators } from 'src/app/validators' + +import { first } from 'rxjs/operators' +import { ReactivationService } from '../../../core/reactivation/reactivation.service' +import { ReactivationLocal } from '../../../types/reactivation.local' +import { BaseForm } from '../BaseForm' + +@Component({ + selector: 'app-form-personal', + templateUrl: './form-personal.component.html', + styleUrls: ['./form-personal.component.scss', '../register2.style.scss', '../register2.scss-theme.scss'], + preserveWhitespaces: true, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormPersonalComponent), + multi: true, + }, + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => FormPersonalComponent), + multi: true, + }, + ], +}) +export class FormPersonalComponent + extends BaseForm + implements OnInit, AfterViewInit { + @Input() reactivation: ReactivationLocal + @ViewChild('firstInput') firstInput: ElementRef + labelInfoAboutName = $localize`:@@register.ariaLabelInfo:info about names` + labelClose = $localize`:@@register.ariaLabelClose:close` + labelConfirmEmail = $localize`:@@register.confirmEmail:Confirm primary email` + labelNameYouMostCommonly = $localize`:@@register.labelNameYouMostMost:The name you most commonly go by` + constructor( + private _register: RegisterService, + private _reactivationService: ReactivationService + ) { + super() + } + + emails: UntypedFormGroup = new UntypedFormGroup({}) + additionalEmails: UntypedFormGroup = new UntypedFormGroup({ + '0': new UntypedFormControl('', { + validators: [OrcidValidators.email], + }), + }) + + ngOnInit() { + this.emails = new UntypedFormGroup( + { + email: new UntypedFormControl('', { + validators: [Validators.required, OrcidValidators.email], + asyncValidators: this._register.backendValueValidate('email'), + }), + additionalEmails: this.additionalEmails, + }, + { + validators: [ + OrcidValidators.matchValues('email', 'confirmEmail', false), + this.allEmailsAreUnique(), + ], + asyncValidators: [ + this._register.backendAdditionalEmailsValidate( + this.reactivation?.isReactivation + ), + ], + updateOn: 'change', + } + ) + + if (!this.reactivation?.isReactivation) { + this.emails.addControl( + 'confirmEmail', + new UntypedFormControl('', { + validators: [Validators.required, OrcidValidators.email], + }) + ) + } + + this.form = new UntypedFormGroup({ + givenNames: new UntypedFormControl('', { + validators: [Validators.required, OrcidValidators.illegalName], + asyncValidators: this._register.backendValueValidate('givenNames'), + }), + familyNames: new UntypedFormControl('', { + validators: [OrcidValidators.illegalName], + }), + emails: this.emails, + }) + + if (this.reactivation?.isReactivation) { + this._reactivationService + .getReactivationData(this.reactivation.reactivationCode) + .pipe(first()) + .subscribe((reactivation) => { + this.emails.patchValue({ + email: reactivation.email, + }) + this.emails.controls['email'].disable() + }) + } + } + + ngAfterViewInit(): void { + // Timeout used to get focus on the first input after the first step loads + setTimeout(() => { + this.firstInput.nativeElement.focus() + }, 100) + } + + allEmailsAreUnique(): ValidatorFn { + return (formGroup: UntypedFormGroup) => { + let hasError = false + const registerForm = + this._register.formGroupToEmailRegisterForm(formGroup) + + const error = { backendErrors: { additionalEmails: {} } } + + Object.keys(registerForm.emailsAdditional).forEach((key, i) => { + const additionalEmail = registerForm.emailsAdditional[key] + if (!error.backendErrors.additionalEmails[additionalEmail.value]) { + error.backendErrors.additionalEmails[additionalEmail.value] = [] + } + const additionalEmailsErrors = error.backendErrors.additionalEmails + if ( + registerForm.email && + additionalEmail.value === registerForm.email.value + ) { + hasError = true + additionalEmailsErrors[additionalEmail.value] = [ + 'additionalEmailCantBePrimaryEmail', + ] + } else { + Object.keys(registerForm.emailsAdditional).forEach( + (elementKey, i2) => { + const element = registerForm.emailsAdditional[elementKey] + if (i !== i2 && additionalEmail.value === element.value) { + hasError = true + additionalEmailsErrors[additionalEmail.value] = [ + 'duplicatedAdditionalEmail', + ] + } + } + ) + } + }) + + if (hasError) { + return error + } else { + return null + } + } + } + + // OVERWRITE + registerOnChange(fn: any) { + this.form.valueChanges.subscribe((value) => { + const emailsForm = this._register.formGroupToEmailRegisterForm( + this.form.controls['emails'] as UntypedFormGroup + ) + const namesForm = + this._register.formGroupToNamesRegisterForm(this.form) || {} + + fn({ ...emailsForm, ...namesForm }) + }) + } + + get emailFormTouched() { + // console.log((this.form.controls?.emails as any)?.email) + return ((this.form.controls.emails as any).controls?.email as any)?.touched + } + + get emailConfirmationFormTouched() { + // console.log((this.form.controls?.emails as any)?.email) + return ((this.form.controls.emails as any).controls?.confirmEmail as any)?.touched + } +} diff --git a/src/app/register2/components/form-terms/form-terms.component.html b/src/app/register2/components/form-terms/form-terms.component.html new file mode 100644 index 0000000000..5745d6534b --- /dev/null +++ b/src/app/register2/components/form-terms/form-terms.component.html @@ -0,0 +1,66 @@ +

Terms of Use

+ + + I consent to the + + privacy policy + + and + + terms of use + + and agree to my data being publicly accessible where marked as “Visible + to Everyone”. + + + + I consent to my data being processed in the United States. + + + More information on how ORCID process your data. + + + To continue creating your ORCID iD you must accept the terms of use and + consent to your data being processed in the United States. + diff --git a/src/app/register2/components/form-terms/form-terms.component.scss b/src/app/register2/components/form-terms/form-terms.component.scss new file mode 100644 index 0000000000..66e0ae0b04 --- /dev/null +++ b/src/app/register2/components/form-terms/form-terms.component.scss @@ -0,0 +1,12 @@ +:host { + margin-bottom: 25px; + display: block; +} + +mat-checkbox { + margin-bottom: 16px; +} + +mat-error { + margin: 8px 0; +} diff --git a/src/app/register2/components/form-terms/form-terms.component.spec.ts b/src/app/register2/components/form-terms/form-terms.component.spec.ts new file mode 100644 index 0000000000..2c933f7bcd --- /dev/null +++ b/src/app/register2/components/form-terms/form-terms.component.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormTermsComponent } from './form-terms.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { RegisterService } from '../../../core/register/register.service' + +describe('FormTermsComponent', () => { + let component: FormTermsComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [FormTermsComponent], + providers: [ + WINDOW_PROVIDERS, + RegisterService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormTermsComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-terms/form-terms.component.ts b/src/app/register2/components/form-terms/form-terms.component.ts new file mode 100644 index 0000000000..f0de5ce315 --- /dev/null +++ b/src/app/register2/components/form-terms/form-terms.component.ts @@ -0,0 +1,69 @@ +import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' +import { + UntypedFormControl, + UntypedFormGroup, + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + Validators, +} from '@angular/forms' +import { ErrorStateMatcher } from '@angular/material/core' +import { RegisterService } from 'src/app/core/register/register.service' +import { environment } from 'src/environments/environment' + +import { BaseForm } from '../BaseForm' + +@Component({ + selector: 'app-form-terms', + templateUrl: './form-terms.component.html', + styleUrls: ['./form-terms.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormTermsComponent), + multi: true, + }, + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => FormTermsComponent), + multi: true, + }, + ], + preserveWhitespaces: true, +}) +// tslint:disable-next-line: class-name +export class FormTermsComponent extends BaseForm implements OnInit, DoCheck { + environment = environment + constructor( + private _register: RegisterService, + private _errorStateMatcher: ErrorStateMatcher + ) { + super() + } + errorState = false + + termsOfUse = new UntypedFormControl('', Validators.requiredTrue) + dataProcessed = new UntypedFormControl('', Validators.requiredTrue) + ngOnInit() { + this.form = new UntypedFormGroup({ + termsOfUse: this.termsOfUse, + dataProcessed: this.dataProcessed, + }) + } + + // OVERWRITE + registerOnChange(fn: any) { + this.form.valueChanges.subscribe((value) => { + const registerForm = + this._register.formGroupTermsOfUseAndDataProcessedRegisterForm( + this.form as UntypedFormGroup + ) + fn(registerForm) + }) + } + + ngDoCheck(): void { + this.errorState = + this._errorStateMatcher.isErrorState(this.termsOfUse, null) || + this._errorStateMatcher.isErrorState(this.dataProcessed, null) + } +} diff --git a/src/app/register2/components/form-visibility/form-visibility.component.html b/src/app/register2/components/form-visibility/form-visibility.component.html new file mode 100644 index 0000000000..0acefbf8ed --- /dev/null +++ b/src/app/register2/components/form-visibility/form-visibility.component.html @@ -0,0 +1,95 @@ + +

+ Visibility settings +

+

+ + Your ORCID iD connects with your ORCID record that can contain links to + your research activities, affiliations, awards, other versions of your + name, and more. You control this content and who can see it. + +

+

+ By default, what visibility should be given to new items added to your ORCID + Record? +

+

+ + + +

+ Everyone + + (87% of users choose this) +
+ + + +
+ Trusted Organizations + (5% of users choose this) +
+
+ + +
+ Only me + + (8% of users choose this) +
+
+ + Please choose a default visibility setting. +

+

+ More information on visibility settings +

+
diff --git a/src/app/register2/components/form-visibility/form-visibility.component.scss b/src/app/register2/components/form-visibility/form-visibility.component.scss new file mode 100644 index 0000000000..f24bbf1354 --- /dev/null +++ b/src/app/register2/components/form-visibility/form-visibility.component.scss @@ -0,0 +1,10 @@ +:host img { + height: 24px; + width: 24px; + margin-left: -4px; + margin-right: 4px; + [dir='rtl'] & { + margin-right: -4px; + margin-left: 4px; + } +} diff --git a/src/app/register2/components/form-visibility/form-visibility.component.spec.ts b/src/app/register2/components/form-visibility/form-visibility.component.spec.ts new file mode 100644 index 0000000000..387ddb0103 --- /dev/null +++ b/src/app/register2/components/form-visibility/form-visibility.component.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormVisibilityComponent } from './form-visibility.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { RegisterService } from '../../../core/register/register.service' + +describe('FormVisibilityComponent', () => { + let component: FormVisibilityComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [FormVisibilityComponent], + providers: [ + WINDOW_PROVIDERS, + RegisterService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(FormVisibilityComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/form-visibility/form-visibility.component.ts b/src/app/register2/components/form-visibility/form-visibility.component.ts new file mode 100644 index 0000000000..2dbfc8716a --- /dev/null +++ b/src/app/register2/components/form-visibility/form-visibility.component.ts @@ -0,0 +1,68 @@ +import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' +import { + UntypedFormControl, + UntypedFormGroup, + NG_ASYNC_VALIDATORS, + NG_VALUE_ACCESSOR, + Validators, +} from '@angular/forms' +import { ErrorStateMatcher } from '@angular/material/core' +import { VISIBILITY_OPTIONS } from 'src/app/constants' +import { RegisterService } from 'src/app/core/register/register.service' + +import { BaseForm } from '../BaseForm' + +@Component({ + selector: 'app-form-visibility', + templateUrl: './form-visibility.component.html', + styleUrls: ['./form-visibility.component.scss'], + preserveWhitespaces: true, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FormVisibilityComponent), + multi: true, + }, + { + provide: NG_ASYNC_VALIDATORS, + useExisting: forwardRef(() => FormVisibilityComponent), + multi: true, + }, + ], +}) +export class FormVisibilityComponent + extends BaseForm + implements OnInit, DoCheck +{ + visibilityOptions = VISIBILITY_OPTIONS + errorState = false + activitiesVisibilityDefault = new UntypedFormControl('', Validators.required) + constructor( + private _register: RegisterService, + private _errorStateMatcher: ErrorStateMatcher + ) { + super() + } + ngOnInit() { + this.form = new UntypedFormGroup({ + activitiesVisibilityDefault: this.activitiesVisibilityDefault, + }) + } + + ngDoCheck(): void { + this.errorState = this._errorStateMatcher.isErrorState( + this.activitiesVisibilityDefault, + null + ) + } + + // OVERWRITE + registerOnChange(fn: any) { + this.form.valueChanges.subscribe((value) => { + const registerForm = this._register.formGroupToActivitiesVisibilityForm( + this.form as UntypedFormGroup + ) + fn(registerForm) + }) + } +} diff --git a/src/app/register2/components/register2.scss-theme.scss b/src/app/register2/components/register2.scss-theme.scss new file mode 100644 index 0000000000..26589908a6 --- /dev/null +++ b/src/app/register2/components/register2.scss-theme.scss @@ -0,0 +1,19 @@ +@use '@angular/material' as mat; +@import 'src/assets/scss/material.orcid-theme.scss'; + +@mixin theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, accent); + $foreground: map-get($theme, foreground); + $background: map-get($theme, background); + + ::ng-deep .valid { + color: mat.get-color-from-palette($accent, 900); + } + .error { + color: map-get($foreground, 'state-warning-dark'); + } +} + +@include theme($orcid-app-theme); diff --git a/src/app/register2/components/register2.style.scss b/src/app/register2/components/register2.style.scss new file mode 100644 index 0000000000..e0223bd64d --- /dev/null +++ b/src/app/register2/components/register2.style.scss @@ -0,0 +1,113 @@ +a { + text-decoration: underline; + font-weight: normal; +} + +mat-card-title img { + margin-bottom: 32px; +} + + +.input-container { + padding-bottom: 24px; +} + +mat-label.orc-font-small-print { + font-weight: bold; + + label { + font-weight: normal; + } +} + +:host ::ng-deep { + + mat-error { + margin-top: 8px; + font-size: 12px; + } + + mat-form-field { + padding-top: 8px; + + .mat-form-field-subscript-wrapper { + padding: 0; + + } + + .mat-form-field-wrapper { + padding-bottom: 2px; + } + + + + + } + + .mat-form-field-appearance-outline { + .mat-form-field-wrapper { + margin: 0; + + .mat-form-field-outline { + top: 0px; + } + + .mat-form-field-prefix { + top: 0.35em; + + mat-icon { + margin-inline-end: 8px; + } + } + + .mat-form-field-flex { + .mat-form-field-infix { + border-top: 0px; + padding: 10px 0 10px 0; + } + } + } + } + + mat-form-field.mat-form-field.mat-primary.mat-form-field-type-mat-input.mat-form-field-appearance-outline.mat-form-field-can-float.mat-form-field-invalid { + margin-bottom: 0px; + + + } + + + + +} + + +// MATERIAL OVERWRITES + +::ng-deep { + + mat-vertical-stepper.orcid-stepper-wizard mat-card mat-form-field, + mat-vertical-stepper.orcid-stepper-wizard mat-card .input-container, + mat-horizontal-stepper.orcid-stepper-wizard mat-card mat-form-field, + mat-horizontal-stepper.orcid-stepper-wizard mat-card .input-container { + margin-bottom: 0px; + + } + + + mat-vertical-stepper.orcid-stepper-wizard, + mat-horizontal-stepper.orcid-stepper-wizard { + mat-card { + + + .step-actions { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + margin-top: 28px; + gap: 12px; + } + } + } + +} diff --git a/src/app/register2/components/step-a/step-a.component.html b/src/app/register2/components/step-a/step-a.component.html new file mode 100644 index 0000000000..4c5ee5d412 --- /dev/null +++ b/src/app/register2/components/step-a/step-a.component.html @@ -0,0 +1,78 @@ + + + +
+ orcid logo +
+ + Create your ORCID iD + + + Thank you for reactivating your ORCID iD. + +
+ This is step 1 of 3 +
+ + +

+ Per ORCID's + terms of use, you may only register for an ORCID iD for yourself. Already have an + ORCID iD? + Sign In +

+
+ + +
+ + + Cancel registration + +
+
+
+
diff --git a/src/app/register2/components/step-a/step-a.component.scss b/src/app/register2/components/step-a/step-a.component.scss new file mode 100644 index 0000000000..9bd1205fc7 --- /dev/null +++ b/src/app/register2/components/step-a/step-a.component.scss @@ -0,0 +1,6 @@ +:host { + display: flex; + flex-direction: column; +} + + diff --git a/src/app/register2/components/step-a/step-a.component.spec.ts b/src/app/register2/components/step-a/step-a.component.spec.ts new file mode 100644 index 0000000000..693ea5d367 --- /dev/null +++ b/src/app/register2/components/step-a/step-a.component.spec.ts @@ -0,0 +1,43 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { StepAComponent } from './step-a.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { RouterTestingModule } from '@angular/router/testing' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' + +describe('StepAComponent', () => { + let component: StepAComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule], + declarations: [StepAComponent], + providers: [ + WINDOW_PROVIDERS, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(StepAComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/step-a/step-a.component.ts b/src/app/register2/components/step-a/step-a.component.ts new file mode 100644 index 0000000000..fada13b9f0 --- /dev/null +++ b/src/app/register2/components/step-a/step-a.component.ts @@ -0,0 +1,76 @@ +import { Component, Input } from '@angular/core' + +import { Router } from '@angular/router' +import { first } from 'rxjs/operators' +import { PlatformInfoService } from 'src/app/cdk/platform-info' +import { ApplicationRoutes } from 'src/app/constants' +import { environment } from 'src/environments/environment' +import { ReactivationLocal } from '../../../types/reactivation.local' +import { BaseStepDirective } from '../BaseStep' + +@Component({ + selector: 'app-step-a', + templateUrl: './step-a.component.html', + styleUrls: ['./step-a.component.scss', '../register2.style.scss', '../register2.scss-theme.scss'], + preserveWhitespaces: true, +}) +export class StepAComponent extends BaseStepDirective { + @Input() reactivation: ReactivationLocal + + constructor(private _platform: PlatformInfoService, private _router: Router) { + super() + } + infoSiteBaseUrl = environment.INFO_SITE + + goBack() { + this._platform + .get() + .pipe(first()) + .subscribe((platform) => { + if (platform.social) { + this._router.navigate([ApplicationRoutes.social], { + queryParams: { + ...platform.queryParameters, + }, + }) + } else if (platform.institutional) { + this._router.navigate([ApplicationRoutes.institutionalLinking], { + queryParams: { + ...platform.queryParameters, + }, + }) + } else { + this._router.navigate([ApplicationRoutes.signin], { + queryParams: { + ...platform.queryParameters, + }, + }) + } + }) + } + + signIn() { + this._platform + .get() + .pipe(first()) + .subscribe((platform) => { + const params = JSON.parse(JSON.stringify(platform.queryParameters)) + if (params['email']) { + delete params['email'] + } + if (params['orcid']) { + delete params['orcid'] + } + + if (params['show_login']) { + delete params['show_login'] + } + + this._router.navigate([ApplicationRoutes.signin], { + queryParams: { + ...params, + }, + }) + }) + } +} diff --git a/src/app/register2/components/step-b/step-b.component.html b/src/app/register2/components/step-b/step-b.component.html new file mode 100644 index 0000000000..e12ce35510 --- /dev/null +++ b/src/app/register2/components/step-b/step-b.component.html @@ -0,0 +1,60 @@ + + + +
+ orcid logo +
+ + Create your ORCID iD + + + Thank you for reactivating your ORCID iD. + +
+ This is step 2 of 3 +
+ + +
+ + +
+ + +
+
+
+
diff --git a/src/app/register2/components/step-b/step-b.component.scss b/src/app/register2/components/step-b/step-b.component.scss new file mode 100644 index 0000000000..4cef8a0ce4 --- /dev/null +++ b/src/app/register2/components/step-b/step-b.component.scss @@ -0,0 +1,4 @@ +:host { + display: flex; + flex-direction: column; +} diff --git a/src/app/register2/components/step-b/step-b.component.spec.ts b/src/app/register2/components/step-b/step-b.component.spec.ts new file mode 100644 index 0000000000..d8ed95bef5 --- /dev/null +++ b/src/app/register2/components/step-b/step-b.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { StepBComponent } from './step-b.component' + +describe('StepBComponent', () => { + let component: StepBComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [StepBComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(StepBComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/step-b/step-b.component.ts b/src/app/register2/components/step-b/step-b.component.ts new file mode 100644 index 0000000000..2edad54dc2 --- /dev/null +++ b/src/app/register2/components/step-b/step-b.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core' + +import { ReactivationLocal } from '../../../types/reactivation.local' +import { BaseStepDirective } from '../BaseStep' + +@Component({ + selector: 'app-step-b', + templateUrl: './step-b.component.html', + styleUrls: ['./step-b.component.scss', '../register2.style.scss', '../register2.scss-theme.scss'], +}) +export class StepBComponent extends BaseStepDirective { + @Input() personalData + @Input() reactivation: ReactivationLocal + + constructor() { + super() + } +} diff --git a/src/app/register2/components/step-c/step-c.component.html b/src/app/register2/components/step-c/step-c.component.html new file mode 100644 index 0000000000..4f05a50218 --- /dev/null +++ b/src/app/register2/components/step-c/step-c.component.html @@ -0,0 +1,73 @@ + + + + + + + Create your ORCID iD + + + Thank you for reactivating your ORCID iD. + + + This is step 3 of 3 + + + +
+ + + +
+ + +
+
+
+ +
diff --git a/src/app/register2/components/step-c/step-c.component.scss b/src/app/register2/components/step-c/step-c.component.scss new file mode 100644 index 0000000000..4cef8a0ce4 --- /dev/null +++ b/src/app/register2/components/step-c/step-c.component.scss @@ -0,0 +1,4 @@ +:host { + display: flex; + flex-direction: column; +} diff --git a/src/app/register2/components/step-c/step-c.component.spec.ts b/src/app/register2/components/step-c/step-c.component.spec.ts new file mode 100644 index 0000000000..db7d3c24ff --- /dev/null +++ b/src/app/register2/components/step-c/step-c.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { StepCComponent } from './step-c.component' + +describe('StepCComponent', () => { + let component: StepCComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [StepCComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(StepCComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/step-c/step-c.component.ts b/src/app/register2/components/step-c/step-c.component.ts new file mode 100644 index 0000000000..4fa84a7c6b --- /dev/null +++ b/src/app/register2/components/step-c/step-c.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core' + +import { BaseStepDirective } from '../BaseStep' +import { ReactivationLocal } from '../../../types/reactivation.local' + +@Component({ + selector: 'app-step-c', + templateUrl: './step-c.component.html', + styleUrls: ['./step-c.component.scss'], +}) +export class StepCComponent extends BaseStepDirective { + @Input() loading + @Input() reactivation: ReactivationLocal + + constructor() { + super() + } +} diff --git a/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.html b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.html new file mode 100644 index 0000000000..2661e57176 --- /dev/null +++ b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.html @@ -0,0 +1 @@ +

top-bar-record-issues works!

diff --git a/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.scss b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.spec.ts b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.spec.ts new file mode 100644 index 0000000000..4255f98cad --- /dev/null +++ b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { TopBarRecordIssuesComponent } from './top-bar-record-issues.component' + +describe('TopBarRecordIssuesComponent', () => { + let component: TopBarRecordIssuesComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [TopBarRecordIssuesComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(TopBarRecordIssuesComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.ts b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.ts new file mode 100644 index 0000000000..c6f3ba3de2 --- /dev/null +++ b/src/app/register2/components/top-bar-record-issues/top-bar-record-issues.component.ts @@ -0,0 +1,12 @@ +import { Component, OnInit } from '@angular/core' + +@Component({ + selector: 'app-top-bar-record-issues', + templateUrl: './top-bar-record-issues.component.html', + styleUrls: ['./top-bar-record-issues.component.scss'], +}) +export class TopBarRecordIssuesComponent implements OnInit { + constructor() {} + + ngOnInit(): void {} +} diff --git a/src/app/register2/pages/register/register.component.html b/src/app/register2/pages/register/register.component.html new file mode 100644 index 0000000000..017de7c72f --- /dev/null +++ b/src/app/register2/pages/register/register.component.html @@ -0,0 +1,51 @@ +
+
+
+
+ + + + + + Security and notifications + + + + Visibility and terms + + + +
+
+
+
diff --git a/src/app/register2/pages/register/register.component.scss b/src/app/register2/pages/register/register.component.scss new file mode 100644 index 0000000000..e8c06d1504 --- /dev/null +++ b/src/app/register2/pages/register/register.component.scss @@ -0,0 +1,15 @@ +:host { + width: 100%; + + ::ng-deep { + .mat-horizontal-stepper-header-container { + display: none; + } + } +} + +mat-vertical-stepper, +mat-horizontal-stepper { + max-width: 100%; + width: 100%; +} \ No newline at end of file diff --git a/src/app/register2/pages/register/register.component.spec.ts b/src/app/register2/pages/register/register.component.spec.ts new file mode 100644 index 0000000000..1967801d07 --- /dev/null +++ b/src/app/register2/pages/register/register.component.spec.ts @@ -0,0 +1,54 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { RegisterComponent } from './register.component' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { Overlay } from '@angular/cdk/overlay' +import { RouterTestingModule } from '@angular/router/testing' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { UntypedFormBuilder } from '@angular/forms' +import { + MatLegacyDialog as MatDialog, + MatLegacyDialogModule as MatDialogModule, +} from '@angular/material/legacy-dialog' +import { RegisterService } from '../../../core/register/register.service' +import { UserService } from '../../../core' +import { SearchService } from '../../../core/search/search.service' + +describe('RegisterComponent', () => { + let component: RegisterComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MatDialogModule, RouterTestingModule], + declarations: [RegisterComponent], + providers: [ + WINDOW_PROVIDERS, + UntypedFormBuilder, + RegisterService, + SearchService, + UserService, + PlatformInfoService, + ErrorHandlerService, + SnackbarService, + MatSnackBar, + MatDialog, + Overlay, + ], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/pages/register/register.component.ts b/src/app/register2/pages/register/register.component.ts new file mode 100644 index 0000000000..197b832d6d --- /dev/null +++ b/src/app/register2/pages/register/register.component.ts @@ -0,0 +1,314 @@ +import { StepperSelectionEvent } from '@angular/cdk/stepper' +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Inject, + OnInit, + ViewChild, +} from '@angular/core' +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { MatStep } from '@angular/material/stepper' +import { Router } from '@angular/router' +import { Observable, combineLatest, forkJoin } from 'rxjs' +import { catchError, first, map, switchMap } from 'rxjs/operators' +import { IsThisYouComponent } from 'src/app/cdk/is-this-you' +import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' +import { WINDOW } from 'src/app/cdk/window' +import { isRedirectToTheAuthorizationPage } from 'src/app/constants' +import { UserService } from 'src/app/core' +import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' +import { RegisterService } from 'src/app/core/register/register.service' +import { ERROR_REPORT } from 'src/app/errors' +import { RequestInfoForm, SearchResults } from 'src/app/types' +import { + RegisterConfirmResponse, + RegisterForm, +} from 'src/app/types/register.endpoint' +import { UserSession } from 'src/app/types/session.local' +import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' +import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' +import { SearchService } from '../../../core/search/search.service' +import { ReactivationLocal } from '../../../types/reactivation.local' + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrls: ['./register.component.scss'], +}) +export class RegisterComponent implements OnInit, AfterViewInit { + @ViewChild('lastStep') lastStep: MatStep + @ViewChild('stepComponentA', { read: ElementRef }) stepComponentA: ElementRef + @ViewChild('stepComponentB', { read: ElementRef }) stepComponentB: ElementRef + @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef + platform: PlatformInfo + FormGroupStepA: UntypedFormGroup + FormGroupStepB: UntypedFormGroup + FormGroupStepC: UntypedFormGroup + isLinear = true + personalData: RegisterForm + backendForm: RegisterForm + loading = false + requestInfoForm: RequestInfoForm | null + thirdPartyAuthData: ThirdPartyAuthData + reactivation = { + isReactivation: false, + reactivationCode: '', + } as ReactivationLocal + + constructor( + private _cdref: ChangeDetectorRef, + private _platformInfo: PlatformInfoService, + private _formBuilder: UntypedFormBuilder, + private _register: RegisterService, + private _dialog: MatDialog, + @Inject(WINDOW) private window: Window, + private _googleTagManagerService: GoogleTagManagerService, + private _user: UserService, + private _router: Router, + private _errorHandler: ErrorHandlerService, + private _userInfo: UserService, + private _searchService: SearchService + ) { + _platformInfo.get().subscribe((platform) => { + this.platform = platform + this.reactivation.isReactivation = this.platform.reactivation + this.reactivation.reactivationCode = this.platform.reactivationCode + }) + } + ngOnInit() { + this._register.getRegisterForm().subscribe() + + this.FormGroupStepA = this._formBuilder.group({ + personal: [''], + }) + this.FormGroupStepB = this._formBuilder.group({ + password: [''], + sendOrcidNews: [''], + }) + this.FormGroupStepC = this._formBuilder.group({ + activitiesVisibilityDefault: [''], + termsOfUse: [''], + captcha: [''], + }) + + combineLatest([this._userInfo.getUserSession(), this._platformInfo.get()]) + .pipe( + first(), + map(([session, platform]) => { + session = session as UserSession + platform = platform as PlatformInfo + + // TODO @leomendoza123 move the handle of social/institutional sessions to the user service + + this.thirdPartyAuthData = session.thirdPartyAuthData + this.requestInfoForm = session.oauthSession + + if (this.thirdPartyAuthData || this.requestInfoForm) { + this.FormGroupStepA = this.prefillRegisterForm( + this.requestInfoForm, + this.thirdPartyAuthData + ) + } + }) + ) + .subscribe() + } + + ngAfterViewInit(): void { + this._cdref.detectChanges() + } + + register() { + this.loading = true + this.lastStep.interacted = true + if ( + this.FormGroupStepA.valid && + this.FormGroupStepB.valid && + this.FormGroupStepC.valid + ) { + this._register + .backendRegisterFormValidate( + this.FormGroupStepA, + this.FormGroupStepB, + this.FormGroupStepC + ) + .pipe( + switchMap((validator: RegisterForm) => { + if (validator.errors.length > 0) { + // At this point any backend error is unexpected + this._errorHandler.handleError( + new Error('registerUnexpectedValidateFail'), + ERROR_REPORT.REGISTER + ) + } + return this._register.register( + this.FormGroupStepA, + this.FormGroupStepB, + this.FormGroupStepC, + this.reactivation, + this.requestInfoForm, + true + ) + }) + ) + .subscribe((response) => { + this.loading = false + if (response.url) { + const analyticsReports: Observable[] = [] + + analyticsReports.push( + this._googleTagManagerService.reportEvent( + 'New-Registration', + this.requestInfoForm || 'Website' + ) + ) + forkJoin(analyticsReports) + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) + ) + .subscribe( + () => this.afterRegisterRedirectionHandler(response), + () => this.afterRegisterRedirectionHandler(response) + ) + } else { + this._errorHandler.handleError( + new Error('registerUnexpectedConfirmation'), + ERROR_REPORT.REGISTER + ) + } + }) + } else { + this.loading = false + } + } + + afterRegisterRedirectionHandler(response: RegisterConfirmResponse) { + if (isRedirectToTheAuthorizationPage(response)) { + this.window.location.href = response.url + } else { + if ( + response.url.indexOf('orcid.org/my-orcid') > 0 && + response.url.indexOf('justRegistered') > 0 + ) { + this.window.scrollTo(0, 0) + this._router + .navigate(['/my-orcid'], { + queryParams: { justRegistered: true }, + }) + .then(() => { + this.window.scrollTo(0, 0) + }) + } else { + this.window.location.href = response.url + } + } + } + + openDialog(duplicateRecordsSearchResults: SearchResults): void { + const duplicateRecords = duplicateRecordsSearchResults['expanded-result'] + const dialogParams = { + width: `1078px`, + height: `600px`, + maxWidth: `90vw`, + + data: { + duplicateRecords, + titleLabel: $localize`:@@register.titleLabel:Could this be you?`, + // tslint:disable-next-line: max-line-length + bodyLabel: $localize`:@@register.bodyLabel:We found some accounts with your name, which means you may have already created an ORCID iD using a different email address. Before creating an account, please confirm that none of these records belong to you. Not sure if any of these are you?`, + contactLabel: $localize`:@@register.contactLabel:Contact us.`, + firstNameLabel: $localize`:@@register.firstNameLabel:First Name`, + lastNameLabel: $localize`:@@register.lastNameLabel:Last Name`, + affiliationsLabel: $localize`:@@register.affiliationsLabel:Affiliations`, + dateCreatedLabel: $localize`:@@register.dateCreatedLabel:Date Created`, + viewRecordLabel: $localize`:@@register.viewRecordLabel:View Record`, + signinLabel: $localize`:@@register.signinLabel:I ALREADY HAVE AN ID, GO BACK TO SIGN IN`, + continueLabel: $localize`:@@register.continueLabel:NONE OF THESE ARE ME, CONTINUE WITH REGISTRATION`, + }, + } + + if (this.platform.tabletOrHandset) { + dialogParams['maxWidth'] = '95vw' + dialogParams['maxHeight'] = '95vh' + } + + const dialogRef = this._dialog.open(IsThisYouComponent, dialogParams) + + dialogRef.afterClosed().subscribe((confirmRegistration) => { + if (!confirmRegistration) { + this._router.navigate(['signin']) + } + }) + } + + selectionChange(event: StepperSelectionEvent) { + + if (this.platform.columns4 || this.platform.columns8) { + this.focusCurrentStep(event) + } + } + + // Fix to material vertical stepper not focusing current header + // related issue https://github.com/angular/components/issues/8881 + focusCurrentStep(event: StepperSelectionEvent) { + let nextStep: ElementRef + if (event.selectedIndex === 0) { + nextStep = this.stepComponentA + } else if (event.selectedIndex === 1) { + nextStep = this.stepComponentB + } else if (event.selectedIndex === 2) { + nextStep = this.stepComponentC + } + // On mobile scroll the current step component into view + if (this.platform.columns4 || this.platform.columns8) { + setTimeout(() => { + const nativeElementNextStep = nextStep.nativeElement as HTMLElement + nativeElementNextStep.scrollIntoView() + }, 200) + } + } + + /** + * Fills the register form. + * Use the data from the Oauth session send by the Orcid integrator + * or + * Use data coming from a third party institution/social entity + * or + * Use empty values + */ + private prefillRegisterForm( + oauthData: RequestInfoForm, + thirdPartyOauthData: ThirdPartyAuthData + ) { + return this._formBuilder.group({ + personal: [ + { + givenNames: + oauthData?.userGivenNames || + thirdPartyOauthData?.signinData?.firstName || + '', + familyNames: + oauthData?.userFamilyNames || + thirdPartyOauthData?.signinData?.lastName || + '', + emails: { + email: + oauthData?.userEmail || + thirdPartyOauthData?.signinData?.email || + '', + confirmEmail: '', + additionalEmails: { '0': '' }, + }, + }, + ], + }) + } +} diff --git a/src/app/register2/pages/register/register2.component.html b/src/app/register2/pages/register/register2.component.html new file mode 100644 index 0000000000..670055304a --- /dev/null +++ b/src/app/register2/pages/register/register2.component.html @@ -0,0 +1,94 @@ +
+
+
+
+ + + + Personal data + + + + Security and notifications + + + + Visibility and terms + + + + + + + + Personal data + + + + Security and notifications + + + + Visibility and terms + + + +
+
+
+
diff --git a/src/app/register2/pages/register/register2.component.scss b/src/app/register2/pages/register/register2.component.scss new file mode 100644 index 0000000000..83134365d6 --- /dev/null +++ b/src/app/register2/pages/register/register2.component.scss @@ -0,0 +1,9 @@ +:host { + width: 100%; +} + +mat-vertical-stepper, +mat-horizontal-stepper { + max-width: 100%; + width: 100%; +} diff --git a/src/app/register2/pages/register/register2.component.ts b/src/app/register2/pages/register/register2.component.ts new file mode 100644 index 0000000000..46fc6822a3 --- /dev/null +++ b/src/app/register2/pages/register/register2.component.ts @@ -0,0 +1,339 @@ +import { StepperSelectionEvent } from '@angular/cdk/stepper' +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + Inject, + OnInit, + ViewChild, +} from '@angular/core' +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { MatStep } from '@angular/material/stepper' +import { Router } from '@angular/router' +import { combineLatest, forkJoin, Observable } from 'rxjs' +import { catchError, first, map, switchMap } from 'rxjs/operators' +import { IsThisYouComponent } from 'src/app/cdk/is-this-you' +import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' +import { WINDOW } from 'src/app/cdk/window' +import { isRedirectToTheAuthorizationPage } from 'src/app/constants' +import { UserService } from 'src/app/core' +import { RegisterService } from 'src/app/core/register/register.service' +import { RequestInfoForm } from 'src/app/types' +import { + RegisterConfirmResponse, + RegisterForm, +} from 'src/app/types/register.endpoint' +import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' +import { ERROR_REPORT } from 'src/app/errors' +import { UserSession } from 'src/app/types/session.local' +import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' +import { ReactivationLocal } from '../../../types/reactivation.local' +import { SearchService } from '../../../core/search/search.service' +import { SearchParameters, SearchResults } from 'src/app/types' +import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' + +@Component({ + selector: 'app-register-2', + templateUrl: './register2.component.html', + styleUrls: ['./register2.component.scss'], +}) +export class Register2Component implements OnInit, AfterViewInit { + @ViewChild('lastStep') lastStep: MatStep + @ViewChild('stepComponentA', { read: ElementRef }) stepComponentA: ElementRef + @ViewChild('stepComponentB', { read: ElementRef }) stepComponentB: ElementRef + @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef + platform: PlatformInfo + FormGroupStepA: UntypedFormGroup + FormGroupStepB: UntypedFormGroup + FormGroupStepC: UntypedFormGroup + isLinear = true + personalData: RegisterForm + backendForm: RegisterForm + loading = false + requestInfoForm: RequestInfoForm | null + thirdPartyAuthData: ThirdPartyAuthData + reactivation = { + isReactivation: false, + reactivationCode: '', + } as ReactivationLocal + + constructor( + private _cdref: ChangeDetectorRef, + private _platformInfo: PlatformInfoService, + private _formBuilder: UntypedFormBuilder, + private _register: RegisterService, + private _dialog: MatDialog, + @Inject(WINDOW) private window: Window, + private _googleTagManagerService: GoogleTagManagerService, + private _user: UserService, + private _router: Router, + private _errorHandler: ErrorHandlerService, + private _userInfo: UserService, + private _searchService: SearchService + ) { + _platformInfo.get().subscribe((platform) => { + this.platform = platform + this.reactivation.isReactivation = this.platform.reactivation + this.reactivation.reactivationCode = this.platform.reactivationCode + }) + } + ngOnInit() { + this._register.getRegisterForm().subscribe() + + this.FormGroupStepA = this._formBuilder.group({ + personal: [''], + }) + this.FormGroupStepB = this._formBuilder.group({ + password: [''], + sendOrcidNews: [''], + }) + this.FormGroupStepC = this._formBuilder.group({ + activitiesVisibilityDefault: [''], + termsOfUse: [''], + captcha: [''], + }) + + combineLatest([this._userInfo.getUserSession(), this._platformInfo.get()]) + .pipe( + first(), + map(([session, platform]) => { + session = session as UserSession + platform = platform as PlatformInfo + + // TODO @leomendoza123 move the handle of social/institutional sessions to the user service + + this.thirdPartyAuthData = session.thirdPartyAuthData + this.requestInfoForm = session.oauthSession + + if (this.thirdPartyAuthData || this.requestInfoForm) { + this.FormGroupStepA = this.prefillRegisterForm( + this.requestInfoForm, + this.thirdPartyAuthData + ) + } + }) + ) + .subscribe() + } + + ngAfterViewInit(): void { + this._cdref.detectChanges() + } + + register() { + this.loading = true + this.lastStep.interacted = true + if ( + this.FormGroupStepA.valid && + this.FormGroupStepB.valid && + this.FormGroupStepC.valid + ) { + this._register + .backendRegisterFormValidate( + this.FormGroupStepA, + this.FormGroupStepB, + this.FormGroupStepC + ) + .pipe( + switchMap((validator: RegisterForm) => { + if (validator.errors.length > 0) { + // At this point any backend error is unexpected + this._errorHandler.handleError( + new Error('registerUnexpectedValidateFail'), + ERROR_REPORT.REGISTER + ) + } + return this._register.register( + this.FormGroupStepA, + this.FormGroupStepB, + this.FormGroupStepC, + this.reactivation, + this.requestInfoForm, + true + ) + }) + ) + .subscribe((response) => { + this.loading = false + if (response.url) { + const analyticsReports: Observable[] = [] + + analyticsReports.push( + this._googleTagManagerService.reportEvent( + 'New-Registration', + this.requestInfoForm || 'Website' + ) + ) + forkJoin(analyticsReports) + .pipe( + catchError((err) => + this._errorHandler.handleError( + err, + ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA + ) + ) + ) + .subscribe( + () => this.afterRegisterRedirectionHandler(response), + () => this.afterRegisterRedirectionHandler(response) + ) + } else { + this._errorHandler.handleError( + new Error('registerUnexpectedConfirmation'), + ERROR_REPORT.REGISTER + ) + } + }) + } else { + this.loading = false + } + } + + afterRegisterRedirectionHandler(response: RegisterConfirmResponse) { + if (isRedirectToTheAuthorizationPage(response)) { + this.window.location.href = response.url + } else { + if ( + response.url.indexOf('orcid.org/my-orcid') > 0 && + response.url.indexOf('justRegistered') > 0 + ) { + this.window.scrollTo(0, 0) + this._router + .navigate(['/my-orcid'], { + queryParams: { justRegistered: true }, + }) + .then(() => { + this.window.scrollTo(0, 0) + }) + } else { + this.window.location.href = response.url + } + } + } + + afterStepASubmitted() { + // Update the personal data object is required after submit since is an input for StepB + + if (!this.reactivation?.isReactivation) { + if (this.FormGroupStepA.valid) { + this.personalData = this.FormGroupStepA.value.personal + const searchValue = + this.personalData.familyNames.value + + ' ' + + this.personalData.givenNames.value + const searchParams: SearchParameters = {} + searchParams.searchQuery = searchValue + + this._searchService.search(searchParams).subscribe((value) => { + if (value['num-found'] > 0) { + this.openDialog(value) + } + }) + } + } + } + + openDialog(duplicateRecordsSearchResults: SearchResults): void { + const duplicateRecords = duplicateRecordsSearchResults['expanded-result'] + const dialogParams = { + width: `1078px`, + height: `600px`, + maxWidth: `90vw`, + + data: { + duplicateRecords, + titleLabel: $localize`:@@register.titleLabel:Could this be you?`, + // tslint:disable-next-line: max-line-length + bodyLabel: $localize`:@@register.bodyLabel:We found some accounts with your name, which means you may have already created an ORCID iD using a different email address. Before creating an account, please confirm that none of these records belong to you. Not sure if any of these are you?`, + contactLabel: $localize`:@@register.contactLabel:Contact us.`, + firstNameLabel: $localize`:@@register.firstNameLabel:First Name`, + lastNameLabel: $localize`:@@register.lastNameLabel:Last Name`, + affiliationsLabel: $localize`:@@register.affiliationsLabel:Affiliations`, + dateCreatedLabel: $localize`:@@register.dateCreatedLabel:Date Created`, + viewRecordLabel: $localize`:@@register.viewRecordLabel:View Record`, + signinLabel: $localize`:@@register.signinLabel:I ALREADY HAVE AN ID, GO BACK TO SIGN IN`, + continueLabel: $localize`:@@register.continueLabel:NONE OF THESE ARE ME, CONTINUE WITH REGISTRATION`, + }, + } + + if (this.platform.tabletOrHandset) { + dialogParams['maxWidth'] = '95vw' + dialogParams['maxHeight'] = '95vh' + } + + const dialogRef = this._dialog.open(IsThisYouComponent, dialogParams) + + dialogRef.afterClosed().subscribe((confirmRegistration) => { + if (!confirmRegistration) { + this._router.navigate(['signin']) + } + }) + } + + selectionChange(event: StepperSelectionEvent) { + if (event.previouslySelectedIndex === 0) { + this.afterStepASubmitted() + } + if (this.platform.columns4 || this.platform.columns8) { + this.focusCurrentStep(event) + } + } + + // Fix to material vertical stepper not focusing current header + // related issue https://github.com/angular/components/issues/8881 + focusCurrentStep(event: StepperSelectionEvent) { + let nextStep: ElementRef + if (event.selectedIndex === 0) { + nextStep = this.stepComponentA + } else if (event.selectedIndex === 1) { + nextStep = this.stepComponentB + } else if (event.selectedIndex === 2) { + nextStep = this.stepComponentC + } + // On mobile scroll the current step component into view + if (this.platform.columns4 || this.platform.columns8) { + setTimeout(() => { + const nativeElementNextStep = nextStep.nativeElement as HTMLElement + nativeElementNextStep.scrollIntoView() + }, 200) + } + } + + /** + * Fills the register form. + * Use the data from the Oauth session send by the Orcid integrator + * or + * Use data coming from a third party institution/social entity + * or + * Use empty values + */ + private prefillRegisterForm( + oauthData: RequestInfoForm, + thirdPartyOauthData: ThirdPartyAuthData + ) { + return this._formBuilder.group({ + personal: [ + { + givenNames: + oauthData?.userGivenNames || + thirdPartyOauthData?.signinData?.firstName || + '', + familyNames: + oauthData?.userFamilyNames || + thirdPartyOauthData?.signinData?.lastName || + '', + emails: { + email: + oauthData?.userEmail || + thirdPartyOauthData?.signinData?.email || + '', + confirmEmail: '', + additionalEmails: { '0': '' }, + }, + }, + ], + }) + } +} diff --git a/src/app/register2/register-routing.module.ts b/src/app/register2/register-routing.module.ts new file mode 100644 index 0000000000..9f62a335f0 --- /dev/null +++ b/src/app/register2/register-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core' +import { Routes, RouterModule } from '@angular/router' +import { RegisterComponent } from './pages/register/register.component' + +const routes: Routes = [ + { + path: '', + component: RegisterComponent, + }, +] + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class RegisterRoutingModule {} diff --git a/src/app/register2/register.module.ts b/src/app/register2/register.module.ts new file mode 100644 index 0000000000..1f6dad684a --- /dev/null +++ b/src/app/register2/register.module.ts @@ -0,0 +1,74 @@ +import { NgModule } from '@angular/core' +import { CommonModule } from '@angular/common' +import { RegisterRoutingModule } from './register-routing.module' +import { RegisterComponent } from './pages/register/register.component' +import { StepAComponent } from './components/step-a/step-a.component' +import { StepBComponent } from './components/step-b/step-b.component' +import { StepCComponent } from './components/step-c/step-c.component' +import { FormPersonalComponent } from './components/form-personal/form-personal.component' +import { FormPasswordComponent } from './components/form-password/form-password.component' +import { FormNotificationsComponent } from './components/form-notifications/form-notifications.component' +import { FormVisibilityComponent } from './components/form-visibility/form-visibility.component' +import { FormTermsComponent } from './components/form-terms/form-terms.component' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +// tslint:disable-next-line: max-line-length +import { FormPersonalAdditionalEmailsComponent } from './components/form-personal-additional-emails/form-personal-additional-emails.component' +import { IsThisYouModule } from '../cdk/is-this-you' +import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field' +import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input' +import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button' +import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox' +import { MatIconModule } from '@angular/material/icon' +import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog' +import { MatStepperModule } from '@angular/material/stepper' +import { MatLegacyRadioModule as MatRadioModule } from '@angular/material/legacy-radio' +import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card' +import { RecaptchaModule } from '../cdk/recaptcha/recaptcha.module' +import { FormAntiRobotsComponent } from './components/form-anti-robots/form-anti-robots.component' +import { A11yLinkModule } from '../cdk/a11y-link/a11y-link.module' +import { BackendErrorComponent } from './components/backend-error/backend-error.component' +import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar' +import { FormDirectivesModule } from '../cdk/form-directives/form-directives.module' +import { TopBarRecordIssuesComponent } from './components/top-bar-record-issues/top-bar-record-issues.component' +import { WarningMessageModule } from '../cdk/warning-message/warning-message.module' +import { MdePopoverModule } from '../cdk/popover' +@NgModule({ + declarations: [ + RegisterComponent, + StepAComponent, + StepBComponent, + StepCComponent, + FormPersonalComponent, + FormPasswordComponent, + FormNotificationsComponent, + FormVisibilityComponent, + FormTermsComponent, + FormPersonalAdditionalEmailsComponent, + FormAntiRobotsComponent, + BackendErrorComponent, + TopBarRecordIssuesComponent, + ], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + RegisterRoutingModule, + MatStepperModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatCheckboxModule, + MatRadioModule, + MatIconModule, + MatDialogModule, + IsThisYouModule, + MdePopoverModule, + MatCardModule, + RecaptchaModule, + A11yLinkModule, + MatProgressBarModule, + FormDirectivesModule, + WarningMessageModule, + ], +}) +export class Register2Module {} From b1731338c8302de84fb6d4ba1b7a24bebcfb507f Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Tue, 14 Nov 2023 10:34:52 -0600 Subject: [PATCH 02/21] Passwords part 1 --- .../form-password.component.html | 212 +++++++----------- .../form-password.component.scss | 3 + .../form-password.component.scss-theme.scss | 11 +- .../form-password/form-password.component.ts | 7 +- .../register2/components/register2.style.scss | 8 +- .../step-b-notifications.component.html | 56 +++++ .../step-b-notifications.component.scss | 4 + .../step-b-notifications.component.ts | 22 ++ .../step-b-notificationscomponent.spec.ts | 24 ++ .../pages/register/register.component.html | 4 +- .../pages/register/register.component.ts | 14 +- src/app/register2/register.module.ts | 46 ++-- 12 files changed, 241 insertions(+), 170 deletions(-) create mode 100644 src/app/register2/components/step-b-notifications/step-b-notifications.component.html create mode 100644 src/app/register2/components/step-b-notifications/step-b-notifications.component.scss create mode 100644 src/app/register2/components/step-b-notifications/step-b-notifications.component.ts create mode 100644 src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts diff --git a/src/app/register2/components/form-password/form-password.component.html b/src/app/register2/components/form-password/form-password.component.html index 7c8077b9be..92925a08fc 100644 --- a/src/app/register2/components/form-password/form-password.component.html +++ b/src/app/register2/components/form-password/form-password.component.html @@ -1,7 +1,8 @@
+ Password + - Password - - A password is required - - - Password must not be the same as your email address - - - Password must match all pattern requirements - - - Password must be between 8 and 256 characters - - -
- -
-
- - + +
+ + +
+
+ + Confirm password + + + + + Retype your password + + + Password must not be the same as your email address + + + + The password and confirmed password must match + +
+ +

Your password has

  1. -
    1 letter or symbol
    +
    At least 1 letter or symbol
  2. -
    1 number
    +
    At least 1 number
- - Confirm password - - - Retype your password - - - Password must not be the same as your email address - - - - The password and confirmed password must match - - - check_circle + check_circle - check_circle + circle - - - -
-

- Must be between 8 and 256 characters long and contain: -

-
    -
  • at least 1 numeral: 0 - 9
  • -
  • - at least 1 of the following: - -
      -
    • - alpha character, case-sensitive a-Z -
    • -
    • - - any of the following symbols:
      - ! @ # $ % ^ * ( ) ~ ` {{ '{ }' }} [ ] | \ & _ -
    • -
    -
  • -
  • - optionally the space character, -
    - i.e ' ' and other punctuation such as . , ; -
  • -
-

- Example: sun% moon2 -

-

- - ORCID does not allow common passwords. Common passwords are insecure, - easily-guessed words or phrases such as 'qwerty123'. - - See the full list of common passwords we don't allow on ORCID -

-
-
-
diff --git a/src/app/register2/components/form-password/form-password.component.scss b/src/app/register2/components/form-password/form-password.component.scss index 5814f8f6f4..528c724645 100644 --- a/src/app/register2/components/form-password/form-password.component.scss +++ b/src/app/register2/components/form-password/form-password.component.scss @@ -10,6 +10,7 @@ ol { li { list-style-type: none; display: flex; + padding-top: 14px; img { margin-inline-end: 4px; } @@ -18,3 +19,5 @@ ol { } } } + + diff --git a/src/app/register2/components/form-password/form-password.component.scss-theme.scss b/src/app/register2/components/form-password/form-password.component.scss-theme.scss index 85ff72cd1f..c28f2bcc6d 100644 --- a/src/app/register2/components/form-password/form-password.component.scss-theme.scss +++ b/src/app/register2/components/form-password/form-password.component.scss-theme.scss @@ -8,9 +8,16 @@ $foreground: map-get($theme, foreground); $background: map-get($theme, background); - ::ng-deep .valid { - color: mat.get-color-from-palette($accent, 900); + ::ng-deep .mat-icon.valid { + color: map-get($foreground, 'brand-primary-dark'); } + + + ::ng-deep .mat-icon{ + color: map-get($background, 'ui-background-lightest'); + } + + } @include form-password($orcid-app-theme); diff --git a/src/app/register2/components/form-password/form-password.component.ts b/src/app/register2/components/form-password/form-password.component.ts index b24e5b3e56..32d5d53dcc 100644 --- a/src/app/register2/components/form-password/form-password.component.ts +++ b/src/app/register2/components/form-password/form-password.component.ts @@ -1,9 +1,9 @@ import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core' import { - UntypedFormControl, - UntypedFormGroup, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, ValidatorFn, Validators, } from '@angular/forms' @@ -20,6 +20,9 @@ import { BaseForm } from '../BaseForm' styleUrls: [ './form-password.component.scss-theme.scss', './form-password.component.scss', + '../register2.scss-theme.scss', + '../register2.style.scss', + ], providers: [ { diff --git a/src/app/register2/components/register2.style.scss b/src/app/register2/components/register2.style.scss index e0223bd64d..bfd259e498 100644 --- a/src/app/register2/components/register2.style.scss +++ b/src/app/register2/components/register2.style.scss @@ -3,7 +3,7 @@ a { font-weight: normal; } -mat-card-title img { +mat-card-title img { margin-bottom: 32px; } @@ -85,6 +85,12 @@ mat-label.orc-font-small-print { ::ng-deep { + mat-vertical-stepper.orcid-stepper-wizard mat-card p, + mat-horizontal-stepper.orcid-stepper-wizard mat-card p { + margin-bottom: 16px; + } + + mat-vertical-stepper.orcid-stepper-wizard mat-card mat-form-field, mat-vertical-stepper.orcid-stepper-wizard mat-card .input-container, mat-horizontal-stepper.orcid-stepper-wizard mat-card mat-form-field, diff --git a/src/app/register2/components/step-b-notifications/step-b-notifications.component.html b/src/app/register2/components/step-b-notifications/step-b-notifications.component.html new file mode 100644 index 0000000000..1f8320866f --- /dev/null +++ b/src/app/register2/components/step-b-notifications/step-b-notifications.component.html @@ -0,0 +1,56 @@ + + + +
+ orcid logo +
+ + Create your ORCID iD + + + Thank you for reactivating your ORCID iD. + +
+ This is step 3 of 4 +
+ + +
+ +
+ + +
+
+
+
diff --git a/src/app/register2/components/step-b-notifications/step-b-notifications.component.scss b/src/app/register2/components/step-b-notifications/step-b-notifications.component.scss new file mode 100644 index 0000000000..4cef8a0ce4 --- /dev/null +++ b/src/app/register2/components/step-b-notifications/step-b-notifications.component.scss @@ -0,0 +1,4 @@ +:host { + display: flex; + flex-direction: column; +} diff --git a/src/app/register2/components/step-b-notifications/step-b-notifications.component.ts b/src/app/register2/components/step-b-notifications/step-b-notifications.component.ts new file mode 100644 index 0000000000..768b5aab06 --- /dev/null +++ b/src/app/register2/components/step-b-notifications/step-b-notifications.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core' + +import { ReactivationLocal } from '../../../types/reactivation.local' +import { BaseStepDirective } from '../BaseStep' + +@Component({ + selector: 'app-step-b-notifications', + templateUrl: './step-b-notifications.component.html', + styleUrls: [ + './step-b-notifications.component.scss', + '../register2.style.scss', + '../register2.scss-theme.scss', + ], +}) +export class StepBNotificationsComponent extends BaseStepDirective { + @Input() personalData + @Input() reactivation: ReactivationLocal + + constructor() { + super() + } +} diff --git a/src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts b/src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts new file mode 100644 index 0000000000..f3363feb47 --- /dev/null +++ b/src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { StepBNotificationsComponent } from './step-b-notifications.component' + +describe('StepBComponent', () => { + let component: StepBNotificationsComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [StepBNotificationsComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(StepBNotificationsComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/pages/register/register.component.html b/src/app/register2/pages/register/register.component.html index 017de7c72f..9480b3f343 100644 --- a/src/app/register2/pages/register/register.component.html +++ b/src/app/register2/pages/register/register.component.html @@ -21,13 +21,13 @@ #stepComponentA > --> - + Security and notifications diff --git a/src/app/register2/pages/register/register.component.ts b/src/app/register2/pages/register/register.component.ts index 197b832d6d..adbbd3ded2 100644 --- a/src/app/register2/pages/register/register.component.ts +++ b/src/app/register2/pages/register/register.component.ts @@ -45,7 +45,7 @@ export class RegisterComponent implements OnInit, AfterViewInit { @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef platform: PlatformInfo FormGroupStepA: UntypedFormGroup - FormGroupStepB: UntypedFormGroup + FormGroupStepBPassword: UntypedFormGroup FormGroupStepC: UntypedFormGroup isLinear = true personalData: RegisterForm @@ -84,10 +84,14 @@ export class RegisterComponent implements OnInit, AfterViewInit { this.FormGroupStepA = this._formBuilder.group({ personal: [''], }) - this.FormGroupStepB = this._formBuilder.group({ + this.FormGroupStepBPassword = this._formBuilder.group({ password: [''], sendOrcidNews: [''], }) + // this.FormGroupStepBNotification = this._formBuilder.group({ + // password: [''], + // sendOrcidNews: [''], + // }) this.FormGroupStepC = this._formBuilder.group({ activitiesVisibilityDefault: [''], termsOfUse: [''], @@ -126,13 +130,13 @@ export class RegisterComponent implements OnInit, AfterViewInit { this.lastStep.interacted = true if ( this.FormGroupStepA.valid && - this.FormGroupStepB.valid && + this.FormGroupStepBPassword.valid && this.FormGroupStepC.valid ) { this._register .backendRegisterFormValidate( this.FormGroupStepA, - this.FormGroupStepB, + this.FormGroupStepBPassword, this.FormGroupStepC ) .pipe( @@ -146,7 +150,7 @@ export class RegisterComponent implements OnInit, AfterViewInit { } return this._register.register( this.FormGroupStepA, - this.FormGroupStepB, + this.FormGroupStepBPassword, this.FormGroupStepC, this.reactivation, this.requestInfoForm, diff --git a/src/app/register2/register.module.ts b/src/app/register2/register.module.ts index 1f6dad684a..fdc9010a76 100644 --- a/src/app/register2/register.module.ts +++ b/src/app/register2/register.module.ts @@ -1,42 +1,44 @@ -import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' -import { RegisterRoutingModule } from './register-routing.module' -import { RegisterComponent } from './pages/register/register.component' +import { NgModule } from '@angular/core' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { FormNotificationsComponent } from './components/form-notifications/form-notifications.component' +import { FormPasswordComponent } from './components/form-password/form-password.component' +import { FormPersonalComponent } from './components/form-personal/form-personal.component' +import { FormTermsComponent } from './components/form-terms/form-terms.component' +import { FormVisibilityComponent } from './components/form-visibility/form-visibility.component' import { StepAComponent } from './components/step-a/step-a.component' import { StepBComponent } from './components/step-b/step-b.component' import { StepCComponent } from './components/step-c/step-c.component' -import { FormPersonalComponent } from './components/form-personal/form-personal.component' -import { FormPasswordComponent } from './components/form-password/form-password.component' -import { FormNotificationsComponent } from './components/form-notifications/form-notifications.component' -import { FormVisibilityComponent } from './components/form-visibility/form-visibility.component' -import { FormTermsComponent } from './components/form-terms/form-terms.component' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { RegisterComponent } from './pages/register/register.component' +import { RegisterRoutingModule } from './register-routing.module' // tslint:disable-next-line: max-line-length -import { FormPersonalAdditionalEmailsComponent } from './components/form-personal-additional-emails/form-personal-additional-emails.component' -import { IsThisYouModule } from '../cdk/is-this-you' -import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field' -import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input' +import { MatIconModule } from '@angular/material/icon' import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button' +import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card' import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox' -import { MatIconModule } from '@angular/material/icon' import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog' -import { MatStepperModule } from '@angular/material/stepper' +import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field' +import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input' +import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar' import { MatLegacyRadioModule as MatRadioModule } from '@angular/material/legacy-radio' -import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card' -import { RecaptchaModule } from '../cdk/recaptcha/recaptcha.module' -import { FormAntiRobotsComponent } from './components/form-anti-robots/form-anti-robots.component' +import { MatStepperModule } from '@angular/material/stepper' import { A11yLinkModule } from '../cdk/a11y-link/a11y-link.module' -import { BackendErrorComponent } from './components/backend-error/backend-error.component' -import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar' import { FormDirectivesModule } from '../cdk/form-directives/form-directives.module' -import { TopBarRecordIssuesComponent } from './components/top-bar-record-issues/top-bar-record-issues.component' -import { WarningMessageModule } from '../cdk/warning-message/warning-message.module' +import { IsThisYouModule } from '../cdk/is-this-you' import { MdePopoverModule } from '../cdk/popover' +import { RecaptchaModule } from '../cdk/recaptcha/recaptcha.module' +import { WarningMessageModule } from '../cdk/warning-message/warning-message.module' +import { BackendErrorComponent } from './components/backend-error/backend-error.component' +import { FormAntiRobotsComponent } from './components/form-anti-robots/form-anti-robots.component' +import { FormPersonalAdditionalEmailsComponent } from './components/form-personal-additional-emails/form-personal-additional-emails.component' +import { StepBNotificationsComponent } from './components/step-b-notifications/step-b-notifications.component' +import { TopBarRecordIssuesComponent } from './components/top-bar-record-issues/top-bar-record-issues.component' @NgModule({ declarations: [ RegisterComponent, StepAComponent, StepBComponent, + StepBNotificationsComponent, StepCComponent, FormPersonalComponent, FormPasswordComponent, From 06d5550c785f80d3bbd3a7eb0bf6319b5b3d9262 Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Tue, 14 Nov 2023 11:19:10 -0600 Subject: [PATCH 03/21] Password 1 --- .../form-visibility.component.html | 54 +++++++----- .../form-visibility.component.scss | 8 +- .../form-visibility.component.ts | 10 ++- .../components/register2.scss-theme.scss | 5 ++ .../step-b-notifications.component.html | 56 ------------ .../step-b-notificationscomponent.spec.ts | 24 ------ .../components/step-c-t/step-c.component.html | 80 ++++++++++++++++++ .../step-c.component.scss | 0 .../{step-c => step-c-t}/step-c.component.ts | 12 ++- .../components/step-c-t/step-c.spec.ts | 24 ++++++ .../step-d.component.html} | 0 .../step-d.component.scss} | 0 .../step-d.component.spec.ts} | 2 +- .../step-d.component.ts} | 12 +-- .../pages/register/register.component.html | 25 ++++-- .../pages/register/register.component.scss | 6 +- .../pages/register/register.component.ts | 20 ++--- src/app/register2/register.module.ts | 8 +- src/assets/vectors/visibility-everyone2.svg | 6 ++ src/assets/vectors/visibility-only-me2.jpg | Bin 0 -> 4385 bytes .../vectors/visibility-trusted-source2.jpg | Bin 0 -> 4186 bytes 21 files changed, 213 insertions(+), 139 deletions(-) delete mode 100644 src/app/register2/components/step-b-notifications/step-b-notifications.component.html delete mode 100644 src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts create mode 100644 src/app/register2/components/step-c-t/step-c.component.html rename src/app/register2/components/{step-c => step-c-t}/step-c.component.scss (100%) rename src/app/register2/components/{step-c => step-c-t}/step-c.component.ts (61%) create mode 100644 src/app/register2/components/step-c-t/step-c.spec.ts rename src/app/register2/components/{step-c/step-c.component.html => step-d/step-d.component.html} (100%) rename src/app/register2/components/{step-b-notifications/step-b-notifications.component.scss => step-d/step-d.component.scss} (100%) rename src/app/register2/components/{step-c/step-c.component.spec.ts => step-d/step-d.component.spec.ts} (91%) rename src/app/register2/components/{step-b-notifications/step-b-notifications.component.ts => step-d/step-d.component.ts} (53%) create mode 100644 src/assets/vectors/visibility-everyone2.svg create mode 100644 src/assets/vectors/visibility-only-me2.jpg create mode 100644 src/assets/vectors/visibility-trusted-source2.jpg diff --git a/src/app/register2/components/form-visibility/form-visibility.component.html b/src/app/register2/components/form-visibility/form-visibility.component.html index 0acefbf8ed..b6dca7fbb8 100644 --- a/src/app/register2/components/form-visibility/form-visibility.component.html +++ b/src/app/register2/components/form-visibility/form-visibility.component.html @@ -1,7 +1,4 @@ -

- Visibility settings -

Your ORCID iD connects with your ORCID record that can contain links to @@ -9,6 +6,10 @@

name, and more. You control this content and who can see it.

+

+ Visibility settings +

+

By default, what visibility should be given to new items added to your ORCID Record? @@ -24,16 +25,19 @@

class="orcid-radio-button-with-icons" >
- Everyone +
+ Everyone - (87% of users choose this) + (87% of users choose this) +
Everyone can see these items
+
class="orcid-radio-button-with-icons" >
- Trusted Organizations - (5% of users choose this) +
+ Trusted Organizations + (5% of users choose this) +
+ Only people and organizations you’ve given permission +
+
class="orcid-radio-button-with-icons" >
- Only me +
+ Only me - (8% of users choose this) + (8% of users choose this) +
+ Items are private and only visible to you +
+
diff --git a/src/app/register2/components/form-visibility/form-visibility.component.scss b/src/app/register2/components/form-visibility/form-visibility.component.scss index f24bbf1354..e0899aacb3 100644 --- a/src/app/register2/components/form-visibility/form-visibility.component.scss +++ b/src/app/register2/components/form-visibility/form-visibility.component.scss @@ -1,8 +1,8 @@ :host img { - height: 24px; - width: 24px; - margin-left: -4px; - margin-right: 4px; + height: 32px; + width: 32px; + margin-left: 7px; + margin-right: 16px; [dir='rtl'] & { margin-right: -4px; margin-left: 4px; diff --git a/src/app/register2/components/form-visibility/form-visibility.component.ts b/src/app/register2/components/form-visibility/form-visibility.component.ts index 2dbfc8716a..f2ef07dcbb 100644 --- a/src/app/register2/components/form-visibility/form-visibility.component.ts +++ b/src/app/register2/components/form-visibility/form-visibility.component.ts @@ -1,9 +1,9 @@ import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' import { - UntypedFormControl, - UntypedFormGroup, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, Validators, } from '@angular/forms' import { ErrorStateMatcher } from '@angular/material/core' @@ -15,7 +15,11 @@ import { BaseForm } from '../BaseForm' @Component({ selector: 'app-form-visibility', templateUrl: './form-visibility.component.html', - styleUrls: ['./form-visibility.component.scss'], + styleUrls: [ + './form-visibility.component.scss', + '../register2.style.scss', + '../register2.scss-theme.scss', + ], preserveWhitespaces: true, providers: [ { diff --git a/src/app/register2/components/register2.scss-theme.scss b/src/app/register2/components/register2.scss-theme.scss index 26589908a6..9342ce246f 100644 --- a/src/app/register2/components/register2.scss-theme.scss +++ b/src/app/register2/components/register2.scss-theme.scss @@ -14,6 +14,11 @@ .error { color: map-get($foreground, 'state-warning-dark'); } + + .text-light { + color: map-get($foreground, 'text-dark-mid'); + + } } @include theme($orcid-app-theme); diff --git a/src/app/register2/components/step-b-notifications/step-b-notifications.component.html b/src/app/register2/components/step-b-notifications/step-b-notifications.component.html deleted file mode 100644 index 1f8320866f..0000000000 --- a/src/app/register2/components/step-b-notifications/step-b-notifications.component.html +++ /dev/null @@ -1,56 +0,0 @@ - - - -
- orcid logo -
- - Create your ORCID iD - - - Thank you for reactivating your ORCID iD. - -
- This is step 3 of 4 -
- - -
- -
- - -
-
-
-
diff --git a/src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts b/src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts deleted file mode 100644 index f3363feb47..0000000000 --- a/src/app/register2/components/step-b-notifications/step-b-notificationscomponent.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing' - -import { StepBNotificationsComponent } from './step-b-notifications.component' - -describe('StepBComponent', () => { - let component: StepBNotificationsComponent - let fixture: ComponentFixture - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [StepBNotificationsComponent], - }).compileComponents() - }) - - beforeEach(() => { - fixture = TestBed.createComponent(StepBNotificationsComponent) - component = fixture.componentInstance - fixture.detectChanges() - }) - - it('should create', () => { - expect(component).toBeTruthy() - }) -}) diff --git a/src/app/register2/components/step-c-t/step-c.component.html b/src/app/register2/components/step-c-t/step-c.component.html new file mode 100644 index 0000000000..d6a3f0da49 --- /dev/null +++ b/src/app/register2/components/step-c-t/step-c.component.html @@ -0,0 +1,80 @@ + + + + + +
+ orcid logo +
+ + Create your ORCID iD + + + Thank you for reactivating your ORCID iD. + +
+ This is step 3 of 3 +
+ + +
+ + + +
+ + + Previous Step + +
+
+
+ +
diff --git a/src/app/register2/components/step-c/step-c.component.scss b/src/app/register2/components/step-c-t/step-c.component.scss similarity index 100% rename from src/app/register2/components/step-c/step-c.component.scss rename to src/app/register2/components/step-c-t/step-c.component.scss diff --git a/src/app/register2/components/step-c/step-c.component.ts b/src/app/register2/components/step-c-t/step-c.component.ts similarity index 61% rename from src/app/register2/components/step-c/step-c.component.ts rename to src/app/register2/components/step-c-t/step-c.component.ts index 4fa84a7c6b..d199a4a7c3 100644 --- a/src/app/register2/components/step-c/step-c.component.ts +++ b/src/app/register2/components/step-c-t/step-c.component.ts @@ -1,14 +1,18 @@ import { Component, Input } from '@angular/core' -import { BaseStepDirective } from '../BaseStep' import { ReactivationLocal } from '../../../types/reactivation.local' +import { BaseStepDirective } from '../BaseStep' @Component({ - selector: 'app-step-c', + selector: 'app-step-ct', templateUrl: './step-c.component.html', - styleUrls: ['./step-c.component.scss'], + styleUrls: [ + './step-c.component.scss', + '../register2.style.scss', + '../register2.scss-theme.scss', + ], }) -export class StepCComponent extends BaseStepDirective { +export class StepCTComponent extends BaseStepDirective { @Input() loading @Input() reactivation: ReactivationLocal diff --git a/src/app/register2/components/step-c-t/step-c.spec.ts b/src/app/register2/components/step-c-t/step-c.spec.ts new file mode 100644 index 0000000000..8ee3edf0c1 --- /dev/null +++ b/src/app/register2/components/step-c-t/step-c.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { StepCTComponent } from './step-c.component' + +describe('StepCComponent', () => { + let component: StepCTComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [StepCTComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(StepCTComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/src/app/register2/components/step-c/step-c.component.html b/src/app/register2/components/step-d/step-d.component.html similarity index 100% rename from src/app/register2/components/step-c/step-c.component.html rename to src/app/register2/components/step-d/step-d.component.html diff --git a/src/app/register2/components/step-b-notifications/step-b-notifications.component.scss b/src/app/register2/components/step-d/step-d.component.scss similarity index 100% rename from src/app/register2/components/step-b-notifications/step-b-notifications.component.scss rename to src/app/register2/components/step-d/step-d.component.scss diff --git a/src/app/register2/components/step-c/step-c.component.spec.ts b/src/app/register2/components/step-d/step-d.component.spec.ts similarity index 91% rename from src/app/register2/components/step-c/step-c.component.spec.ts rename to src/app/register2/components/step-d/step-d.component.spec.ts index db7d3c24ff..b1ec6c6df5 100644 --- a/src/app/register2/components/step-c/step-c.component.spec.ts +++ b/src/app/register2/components/step-d/step-d.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { StepCComponent } from './step-c.component' +import { StepCComponent } from './step-d.component' describe('StepCComponent', () => { let component: StepCComponent diff --git a/src/app/register2/components/step-b-notifications/step-b-notifications.component.ts b/src/app/register2/components/step-d/step-d.component.ts similarity index 53% rename from src/app/register2/components/step-b-notifications/step-b-notifications.component.ts rename to src/app/register2/components/step-d/step-d.component.ts index 768b5aab06..a24cd37c86 100644 --- a/src/app/register2/components/step-b-notifications/step-b-notifications.component.ts +++ b/src/app/register2/components/step-d/step-d.component.ts @@ -4,16 +4,16 @@ import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseStepDirective } from '../BaseStep' @Component({ - selector: 'app-step-b-notifications', - templateUrl: './step-b-notifications.component.html', + selector: 'app-step-d', + templateUrl: './step-d.component.html', styleUrls: [ - './step-b-notifications.component.scss', + './step-d.component.scss', '../register2.style.scss', - '../register2.scss-theme.scss', + '../register2.scss-theme.scss' ], }) -export class StepBNotificationsComponent extends BaseStepDirective { - @Input() personalData +export class StepDComponent extends BaseStepDirective { + @Input() loading @Input() reactivation: ReactivationLocal constructor() { diff --git a/src/app/register2/pages/register/register.component.html b/src/app/register2/pages/register/register.component.html index 9480b3f343..90c64f16b4 100644 --- a/src/app/register2/pages/register/register.component.html +++ b/src/app/register2/pages/register/register.component.html @@ -1,7 +1,7 @@
-
+
- --> - + + Security and notifications + + + Security and notifications + + --> Visibility and terms - + >
diff --git a/src/app/register2/pages/register/register.component.scss b/src/app/register2/pages/register/register.component.scss index e8c06d1504..44be1dfeba 100644 --- a/src/app/register2/pages/register/register.component.scss +++ b/src/app/register2/pages/register/register.component.scss @@ -12,4 +12,8 @@ mat-vertical-stepper, mat-horizontal-stepper { max-width: 100%; width: 100%; -} \ No newline at end of file +} + +.max-witdth { + max-width: 580px; +} diff --git a/src/app/register2/pages/register/register.component.ts b/src/app/register2/pages/register/register.component.ts index adbbd3ded2..595c6fc206 100644 --- a/src/app/register2/pages/register/register.component.ts +++ b/src/app/register2/pages/register/register.component.ts @@ -45,7 +45,8 @@ export class RegisterComponent implements OnInit, AfterViewInit { @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef platform: PlatformInfo FormGroupStepA: UntypedFormGroup - FormGroupStepBPassword: UntypedFormGroup + FormGroupStepB: UntypedFormGroup + FormGroupStepCT: UntypedFormGroup FormGroupStepC: UntypedFormGroup isLinear = true personalData: RegisterForm @@ -84,14 +85,14 @@ export class RegisterComponent implements OnInit, AfterViewInit { this.FormGroupStepA = this._formBuilder.group({ personal: [''], }) - this.FormGroupStepBPassword = this._formBuilder.group({ + this.FormGroupStepB = this._formBuilder.group({ password: [''], sendOrcidNews: [''], }) - // this.FormGroupStepBNotification = this._formBuilder.group({ - // password: [''], - // sendOrcidNews: [''], - // }) + this.FormGroupStepCT = this._formBuilder.group({ + activitiesVisibilityDefault: [''], + }) + this.FormGroupStepC = this._formBuilder.group({ activitiesVisibilityDefault: [''], termsOfUse: [''], @@ -130,13 +131,13 @@ export class RegisterComponent implements OnInit, AfterViewInit { this.lastStep.interacted = true if ( this.FormGroupStepA.valid && - this.FormGroupStepBPassword.valid && + this.FormGroupStepB.valid && this.FormGroupStepC.valid ) { this._register .backendRegisterFormValidate( this.FormGroupStepA, - this.FormGroupStepBPassword, + this.FormGroupStepB, this.FormGroupStepC ) .pipe( @@ -150,7 +151,7 @@ export class RegisterComponent implements OnInit, AfterViewInit { } return this._register.register( this.FormGroupStepA, - this.FormGroupStepBPassword, + this.FormGroupStepB, this.FormGroupStepC, this.reactivation, this.requestInfoForm, @@ -254,7 +255,6 @@ export class RegisterComponent implements OnInit, AfterViewInit { } selectionChange(event: StepperSelectionEvent) { - if (this.platform.columns4 || this.platform.columns8) { this.focusCurrentStep(event) } diff --git a/src/app/register2/register.module.ts b/src/app/register2/register.module.ts index fdc9010a76..22d137884f 100644 --- a/src/app/register2/register.module.ts +++ b/src/app/register2/register.module.ts @@ -8,7 +8,7 @@ import { FormTermsComponent } from './components/form-terms/form-terms.component import { FormVisibilityComponent } from './components/form-visibility/form-visibility.component' import { StepAComponent } from './components/step-a/step-a.component' import { StepBComponent } from './components/step-b/step-b.component' -import { StepCComponent } from './components/step-c/step-c.component' +import { StepDComponent } from './components/step-d/step-d.component' import { RegisterComponent } from './pages/register/register.component' import { RegisterRoutingModule } from './register-routing.module' // tslint:disable-next-line: max-line-length @@ -31,15 +31,15 @@ import { WarningMessageModule } from '../cdk/warning-message/warning-message.mod import { BackendErrorComponent } from './components/backend-error/backend-error.component' import { FormAntiRobotsComponent } from './components/form-anti-robots/form-anti-robots.component' import { FormPersonalAdditionalEmailsComponent } from './components/form-personal-additional-emails/form-personal-additional-emails.component' -import { StepBNotificationsComponent } from './components/step-b-notifications/step-b-notifications.component' +import { StepCTComponent } from './components/step-c-t/step-c.component' import { TopBarRecordIssuesComponent } from './components/top-bar-record-issues/top-bar-record-issues.component' @NgModule({ declarations: [ RegisterComponent, StepAComponent, StepBComponent, - StepBNotificationsComponent, - StepCComponent, + StepCTComponent, + StepDComponent, FormPersonalComponent, FormPasswordComponent, FormNotificationsComponent, diff --git a/src/assets/vectors/visibility-everyone2.svg b/src/assets/vectors/visibility-everyone2.svg new file mode 100644 index 0000000000..028475fbeb --- /dev/null +++ b/src/assets/vectors/visibility-everyone2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/vectors/visibility-only-me2.jpg b/src/assets/vectors/visibility-only-me2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..75dff40feed40e825da26b5fd003bdc061d974fc GIT binary patch literal 4385 zcmbuBdo+!AAx&->HRY608acTZGDn)+ zjcZgcF*D6!BzK0w%rrz{l$aPZKj-;9>-Vhn{PnEow?FIi*JrQ2)@$$m-uok(5X}OL zFxQi=fY>fE;J2Lvh^7D+K%>tTts?(zCAkk=qj&oU(Dwv4Qb>V7qK{X+(ZMqn#z8(TYj2bW(@xVrsz5{C5l z@kO2S^FMzf^kP^zCL%U2{%Qg)@mlhol+?7l_tLYnb8_?YA3ZL3TKbIiysW&UvZ1l5 z`9(|X%U9hLD(x-(ub$qa;gQj?@d@T6i_Q7Ootyu>u(YbOa}yCfvUC8Yl565ExqbH$Y;Bo9J<*?Y`WDmYqM$KsaszT*!|>bqogExmZ4 zkeESP6+J7K{_5Xo|3&tH1H1kIBKtSk|8PwLa^hk;#S>Qopn$M7X||Ne^TH;hd2npX zRcF)H(I%k<{~^b++)rS_cYlUH5nRRZ*z$0XEnmErM>0{3<|lMJ=mz=8M+W8fuZQL> zkI~(Sn~k?pTF~j7%XSPqWAju4Rn>cM*)KWy+>yBwq_SN=@w2&+Tn%fp@Gbv-i-eqD zxN>3rR@$u&$!NC+8ZH5*y#{h*o!;$?po8pF9!9>D(45GZTXIKam7dr>=v$4*MOV&p zd~*UZ-Zgj0#|auUJjND61aw`1&i)B)R%xjWj6`cWp?Hm5`Pm;tK$yUWuUol@mJ>kA zFpbNeq!~2-+P5iMu}RCfsf~9WZN)E7PsR+oMMAW#6Ne=($gkRIUR1(&X3-Mfhn&4Tu}-B z9L7w!#%tW9f$b)dj0WZm%P(lR6yt!AL-t%z$4OqoQpT3Q;K*3;s?_ZyOlM}X+URU+ zz{3O*K4A4@CsC&;x1&XUp?Tz7j*9#J0RH8u%OPQ@Y_*UC*Png1%F!x&Dr-{S$6f;| z&&`!RVvo*jaF!A;7WZ3v5pst;9JQa#74OGkp9F^`zr#r-NAodV6eb&oH6ZHTz#tZH z4&C8gV0jT1M($+g=&R|CuiX#<5~Cs@IlV0!%kJERSKscaT_Y>vPyuaqgK_9dbHuPo zlRUON!LliDfU$~j8~VqKf#y#6ri}A)(?)}F?^JyULLRIj$7fd^0^eOH38&r&t=MBa zseZI?WdRDlr%CP(DR!P!Y1wVaJ|m=Osc*_pWvW*hjI@vB)lmCv9>EWzCzYrmonC9w zwB(1V=NdKi!4uVJ@iD6JTb3pG3M@8LAtc(eg3!$w@(Gpe1l^?t*JtHAIm_at;QQ*e znVRjic5a*M)oe<9WV{?oW!&c5v-J&??@t+WJP&27SQo2H4JS-4_2^i1WCh$S1r~e%4$IC~?Ra#S7ok*-W7u=3 z0P}267}Ck}&YY||yG`zlnt1?K##%exe=i+AZ8qAWDKM_XXn(ziprCgxs$Nq_^Y3Sp z9-Hn6oLSozTYpKtZjwtz+(#G~BvR@&rNZpy#ch;OrgKY-|9<^`8hf0M1qt7G?z?Fw(BMOOg>R>A zd1c9MP!ewZ#1J$babI1t|1k*{JG*?J1Kop(L7z<`Eheasr&qTf*y$1(*uo@a=<5uE zh!hSquG#4(ui6jw#a?pt$n0zABb(7e>i7{>tKJp!_8CUVG8Yq4dZ|Zm&<{71l7>}B zeUCen*7d#^>c}AyUZN%3Q~54t9Jn^C)_`5C*Betrw|vWvsrEIv-5RTkP`XEtb~ZMq zhv>QzmwCKy?r1Cw&Ft?m{sQs|58lo|cbbZTkQo&rvsi;)F?&D+@bwIKL>WAzhI5?F z&vZU0FyuG0j_l^7K0MDhzZ)dk+%y`eof33#K#&-0n}79ZQ@=h>jYKOiOlHtt z)Q8%X7vNuc%ukr9QlpQ^QKX>qs~Se>l+ZJ6m(M<8eV%J#p%2_4M0hg3c+rOABDWJ` z2b1B}ESt(>BcI~9{0{#L+3J zZwohdcxCBpQErrJ-GOF9$o!dzO|u#-rhH-i;V)k6<5vfID|=~&-w8O@{eiePYyW`kWm1eo0p-%@hHWq5Npp%tU54Q9cQyt@cip;HsG0jX-yv z4XoVDuq!m{4ttrQj&CYwegAp_e_7SayEN%%M)98F$RK}%^gf72g zAXNFb)Q2Bj*yb|0QmU5P_|sQ@dlyY3kB!!@)p$APzRP%qzWb+y1RhucM)?dNB?$TgFpo; z1-@)nRM*;P?c}O;I=U%PQ~i(VXqcJJi?mI{odqQc+L0o33;f5Qs!)x`e*r4NTfgDY z9qn_QcJx6R7(|qR2FOba(w(2CuZRHRdb2gW@{On~^QYaTj&q+DxqW=|16NGmqzfD9%3AP)Z|UI8ci^~AmVL~Y zdDS;p@N*F6YsB@{{7#0w-&FVNlnm>ls_-BRVk7QBb9&8K)Hgr$KeHqL1R`6-U(a`IHJ!U)v7@<-_eS~ zEjoshl8eGin%vO8`(*z+Rs5*3>fd9v6mM!C`-36evwH_Io}WEyyChzF zbDXH)1dUDP*>aX!wre+;NLsAiL!XfUOXD}F^HAVWPbXvyhPHWgXE7S9dJ<~Hj? z=Q>LgGA6VR&-NLlKAyJO*ZMm73emThb+sbrR>6Z4iQ>q(T@2)4x|K5^1x1P9iC}TM+4<(p4=4cuAT&WVzoyQ4O_F5LP(nvlI zr*mDows2*_sB@yNy{e%MF7|tE7UK0leB50VYyQhh_+w|f6rt_0~)EFad()(L1Yj$I}Y=oPL67Y zy;@ZuIEAv$9zB7HRC~y9G_e_QkyQz|TGE9zp7zLle13~qh_z;$44!2-o|>|tx*D~B zCyG20Cn2aCPqW4b%HYt8MONEj2+MpA&a>PN%z`Vj2puHXrxmk1J5kK7pys5XpMg<7 z@+y*ikLr^|qxx=$fT8n3nZhj<=FKi*gZb~ylJ2pg6}ah>KeRRRsBx>ghhO8#_kzFy zF4gVXf7L&3+Lm&!8+6hpolR6(x$%sD`RyiKdU-iaYU}zfT#$w?<&hJq7X*PLX>-=W zH({+`-OQV#+sU$4EO&Xh@BJqZibYvDkA)!I4>k$C9}nxtbsVm8F(Md-YS`bY&8kZI zt6|}$8NMD9#X?Mrr%T>z=zI?fV~s$ zBz{`A&|v6N{5$c0;WBz*XeGuWN7cr5tl#GhEn4EP=H8>?PwO^|;0F{4C8*u^OWc%G z$x`rU*)^7L#+f4=ucDC>yN)45z>&e9u6_j<{mh^;1)unWi>Sx5#%@Zt`z!<=mkwFV z1gUbX(x?MZ97ZbjCg<+3zfNAW$(%c|-m}vHQJoj77-(fYe7jg^hMSu8NGZ^SudwFo zOAKh8GH=KZ$1k-(5-V}U?hezzrADim%%zpRxb9ys(tLb}chqxu+Tpqu`a}+v*qP7n;VBEGgx>w3jT3!|TpEM#Vekj$kD)Xx0 ze%=I~l9)4@oK=-cN@IN2XaONV<|Iw<>@WC7$9Uz0f{BkrK%;^{F^0|&0aW8mnTVuj z&`g@uwwWV?!Tfqt-sQ&i8%lVM;?hDRJ z6uY8)iGM$pVW`bt~UT31ciOA|{IXxhl?=$GCn z;L|m*Mg@II?VbtPOt2oTBcVzg?;xAb)M%zqP65 zCE_!D->jDAEZ;t~HPM$2cWRDxicGn5iM>$<_e^oXT|?A3>|6L=i;e=OxmOqf literal 0 HcmV?d00001 diff --git a/src/assets/vectors/visibility-trusted-source2.jpg b/src/assets/vectors/visibility-trusted-source2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4338cf99800002701ceb6ebe3006b6c571c0fe7 GIT binary patch literal 4186 zcmbu>XH*l|wgBMJ1d$ec6OHT^8T#8`|}*Y3*-|&tAFu?giA2LO01;8 z?+>v8sx~csQmzwhHN%j&^pk?pGO}{=>gO~xwLnJ3CZ=FB$fe6yZ0)YvL*3jxJiWYq zd_%8?g-6_gBjOVhZ{I;CCEv@)%*xKm%_}M{L6?@5S709FYU`fh>l+$d+uF$;&pNxh zDg9I$ec<)r(B#zg%Cy-0@+_Fj^gG&92J~rzKEJw zO4vMm7}7bdo}v55XiDmVueYC^>h~*CcB(uoc0z_w20z2f0{M;7%Nw(*IBtfwn6nzK zO7=JF2m4Oo@2JJ63*}a;Bd@}_j5B=V8Iy6JcT`jlOB{y&Hvr6YoGRo1- zQP_^^nMddq#wdYE(U?*+5bBS+BnDlh9qe;bKuI7+utrfMcDMzCzuWPochAENTFUIfpsv*mKiXw_A?~m?kT+S(8?Za&p4MxIOvEe}S z`+;d4N&OS7Paz^pf255^OzKim5qV0wvDsoCRHr7dj8T0v8R3bz)M4QV*JoHkBptM= zR%6`2Z;*;7ApMeJdT5J?ZZTSSuejez7E4KyD_SMtWkWWDwZi2pC;h*~D@K9msPThp zYox;oic6Hnmfl{m|6KG?%N^$v|{3Hz+-=6$h$y3mA@$*7#yNG4NptgQOKwW0g|dglhVAl+eEOYtfng zUc~YUW4(l<7C$w{h^4%29%tlcL-T$&C&fg4nE|F$DcV9h6i#{V1WNQ=qr^BVRr{1# zn3(3ZsS5<@c{EOCDe%Zo?#B$QC4$`RoC^3fzGD&*llwdQC3pu>K{9qY1;&Yz=}!at zEx;FMN?$NO#tkMm3$skVdQJs6)Q)YD@;PU24uHg5bc&4Ir-8rjzdL;^O55uEK(l}* z@)E7nU&M`Sc1MDZ7Y}>GR1zB{l`kr(Dg>&~+)H9M4f})f$sQZ+8O?5C-YZ?h9d*Gey8=x8Z3%25} z?u6Yd6VwnZ`i~kb2wb{Gcq<`Ih9zPw%zdzK*Mj~!c;Uc8Z($L>W=pr8@MJkc&Mqtr zDHhFZ{NOqO{ZQXg6wUCtb#b7p9fyhO4MV*)V9ofmW~qP4rdXYlK|@+sj{)VHp@>x@ zX=1R!mbGErTJ*PJ+MlNkh3w>*ZAz#6Xbi)f+Cl?TP8Ouh3v?oMFE%Cb7_f8Xeil`K zFiV{wql*qdPe1)+^wIVhaP7I!v)eMyJnul4vt^{)26-9%DSa6^efQ!-;z!ep=jnm@ zL-9AKKm+vHfv}1~Q(Zd!aZ!UJ?39F;aBjhsbALS{xM48GajxCB*7v1PN!ZEVqlD^x z(iP3?cc#9Hh0`XQp;B-#8JC}d3vcgOQ1)gW>2z^qmTR&+d-*1etUrn>aG>L~_bS`r z4Ry4JCBz*_I&ScfJ|oUmq7nI_O7)D{mD2aWlzue~a7g?yS`+(T_rnBZ{GPY95GOwC z7(lkLTlRlh$Z0E#e_C1?j~a`usD--|`ehNEM{d~P2ZKGUggganUOX2WT!LriYwpN&9R*oAT;DusOXk@ph+~c`leIn~bA#g}@nUjZ0B0loL zpHBN7bGBj6Izo+~6KAM%x_e4yoO!0GFnewScOUFc8@J3q_udR48lZChh}lrJ^4(Epoy zWfm{G_#?0{+AuvdOrhs_POHF}z{kAry#nlEROdK|8eVCBO~FD+h|2(L*X+<)@_Vg!PuO10tf-4Cq}yF`)}V`+(RV6 zjv{(eX97<~Tj(S=AMeS`uk89ZKwx8b<%KDyaBV4=+ zm1L8LF<+{z_1I|xx)MmYU!Jkav$+gqxf>=D!-=&UM@AFY9d34*@Q4Q+P0vi41uY79 zWEY2kwZ((_{5OvbUs>Bb87!wtQ`nxS*+@~UlI*a)aZ81?2UA(3_YA(DledG0HfuI0 zhKoE=Dq&3%JSOWmL2(2x)~ECawWF1s9-TqM!-bCDE*UbF6h>PTu$S~`?AeGPk1o*B zMmWBLd@RgZUDj4_J?~yohUMdWCXtF93qR3vY*zrQ^>ra0^;-Hz)GRPJx8H`88PH%f30|+j!f=HcEVBczm zlTS9+{kQ6{HvG%i_K6?zzNuh;hn_r0<`#UPjvleazW|CS|5Led^I-qG!cz{ z8t(H+qlBSaz|Zu_708iWLQ9pWRNEJYAKE`!{U&m$jM?VDVoY=G3!A?!SV9PiMPepb zGeOSx$J{C_;gfi|83NcXqxJt#81!fUb<<)AW`U)$bQk4@|E_3VAN1h?0d! zKD6`ocM!*bKcaD;Gp2iF+5KwE8}7KA(N)N|aq$&RGYGin7+~Pu+wq}GbJw{q_(RZ6 zkFgWwalVh>Rz4KyhCQMQ|MP;%vglsCKaV#_0Shan_1FtzI?n|KC&y(9C=+Rr&og9JwhDFG8;CFLLgA-e@AjRfBi?d2=Q8yx-}6 zf8%qJzY=RT{Z&*Rd<=kEC>=nM@Z~zW8AeKLhq*P?Eup2-quQy^)Pe}Lk7tm+z~A&U zS{D^-Xj#MTw`tK**EY%e@pXdv-{K2Weq}b9*JB@rV6%rChN}x*%IFCO9LE%61jPg7 dLGua#F%dKSQNwR-Q4yLi{hF8VkN@L%{-5V3zh(dc literal 0 HcmV?d00001 From 337b4c579e7061f79eb547875ee2a74f0c74ed6b Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Tue, 14 Nov 2023 15:37:38 -0600 Subject: [PATCH 04/21] register2service --- .../register2/register2.backend-validators.ts | 254 ++++++++++++++++++ .../core/register2/register2.form-adapter.ts | 136 ++++++++++ src/app/core/register2/register2.service.ts | 183 +++++++++++++ .../pages/register/register.component.ts | 13 +- src/app/register2/register-routing.module.ts | 6 +- src/app/register2/register.module.ts | 4 +- 6 files changed, 584 insertions(+), 12 deletions(-) create mode 100644 src/app/core/register2/register2.backend-validators.ts create mode 100644 src/app/core/register2/register2.form-adapter.ts create mode 100644 src/app/core/register2/register2.service.ts diff --git a/src/app/core/register2/register2.backend-validators.ts b/src/app/core/register2/register2.backend-validators.ts new file mode 100644 index 0000000000..0dc62cdb35 --- /dev/null +++ b/src/app/core/register2/register2.backend-validators.ts @@ -0,0 +1,254 @@ +import { HttpClient } from '@angular/common/http' +import { + AbstractControl, + AsyncValidatorFn, + UntypedFormGroup, + ValidationErrors, +} from '@angular/forms' +import { Observable, of } from 'rxjs' +import { catchError, map, retry } from 'rxjs/operators' +import { Constructor } from 'src/app/types' +import { RegisterForm } from 'src/app/types/register.endpoint' +import { environment } from 'src/environments/environment' +import { ErrorHandlerService } from '../error-handler/error-handler.service' + +interface HasHttpClientAndErrorHandler { + _http: HttpClient + _errorHandler: ErrorHandlerService +} + +interface HasFormAdapters { + formGroupToEmailRegisterForm(formGroup: UntypedFormGroup): RegisterForm + formGroupToPasswordRegisterForm(formGroup: UntypedFormGroup): RegisterForm + formGroupToFullRegistrationForm( + StepA: UntypedFormGroup, + StepB: UntypedFormGroup, + StepC: UntypedFormGroup, + StepD: UntypedFormGroup, + + ): RegisterForm +} + +export function Register2BackendValidatorMixin< + T extends Constructor +>(base: T) { + return class RegisterBackendValidator extends base { + constructor(...args: any[]) { + super(...args) + } + formInputs = { + givenNames: { + validationEndpoint: 'validateGivenNames', + }, + familyNames: { + validationEndpoint: 'validateFamilyNames', + }, + email: { + validationEndpoint: 'validateEmail', + }, + emailsAdditional: { + validationEndpoint: 'validateEmailsAdditional', + }, + passwordConfirm: { + validationEndpoint: 'validatePasswordConfirm', + }, + password: { + validationEndpoint: 'validatePassword', + }, + } + + validateRegisterValue( + controlName: string, + value: RegisterForm + ): Observable { + return this._http + .post( + environment.API_WEB + + `oauth/custom/register/${this.formInputs[controlName].validationEndpoint}.json`, + value + ) + .pipe( + retry(3), + catchError((error) => this._errorHandler.handleError(error)) + ) + } + + validateAdditionalEmailsReactivation( + value: RegisterForm + ): Observable { + return this._http + .post( + `${environment.API_WEB}reactivateAdditionalEmailsValidate.json`, + value + ) + .pipe( + retry(3), + catchError((error) => this._errorHandler.handleError(error)) + ) + } + + backendValueValidate( + controlName: 'givenNames' | 'familyNames' | 'email' | 'password' + ): AsyncValidatorFn { + return ( + control: AbstractControl + ): Observable => { + if (control.value === '') { + return of(null) + } + const value = {} + value[controlName] = { value: control.value } + + return this.validateRegisterValue(controlName, value).pipe( + map((res) => { + if (res[controlName].errors && res[controlName].errors.length > 0) { + const error = { + backendError: res[controlName].errors, + } + return error + } + return null + }) + ) + } + } + + backendAdditionalEmailsValidate(reactivate: boolean): AsyncValidatorFn { + return ( + formGroup: UntypedFormGroup + ): Observable => { + const value: RegisterForm = this.formGroupToEmailRegisterForm(formGroup) + if (!value.emailsAdditional || value.emailsAdditional.length === 0) { + return of(null) + } + + if (reactivate) { + return this.validateAdditionalEmailsReactivation(value).pipe( + map((response) => { + // Add errors to additional emails controls + return this.setFormGroupEmailErrors(response, 'backendErrors') + }) + ) + } + + return this.validateRegisterValue('emailsAdditional', value).pipe( + map((response) => { + // Add errors to additional emails controls + return this.setFormGroupEmailErrors(response, 'backendErrors') + }) + ) + } + } + + backendPasswordValidate(): AsyncValidatorFn { + return ( + formGroup: UntypedFormGroup + ): Observable => { + const value: RegisterForm = + this.formGroupToPasswordRegisterForm(formGroup) + if (value.password.value === '' || value.passwordConfirm.value === '') { + return of(null) + } + return this.validateRegisterValue('password', value).pipe( + map((response) => { + // Add errors to additional emails controls + return this.setFormGroupPasswordErrors(response, 'backendErrors') + }) + ) + } + } + + backendRegisterFormValidate( + StepA: UntypedFormGroup, + StepB: UntypedFormGroup, + StepC: UntypedFormGroup, + StepD: UntypedFormGroup, + + type?: 'shibboleth' + ): Observable { + const registerForm = this.formGroupToFullRegistrationForm( + StepA, + StepB, + StepC, + StepD + ) + return this._http + .post(`${environment.API_WEB}register.json`, registerForm) + .pipe( + retry(3), + catchError((error) => this._errorHandler.handleError(error)) + ) + } + + public setFormGroupEmailErrors( + registerForm: RegisterForm, + errorGroup: string + ) { + let hasErrors = false + const error = {} + error[errorGroup] = { + additionalEmails: {}, + email: [], + } + + registerForm.emailsAdditional.forEach((responseControl) => { + if (responseControl.errors && responseControl.errors.length > 0) { + hasErrors = true + error[errorGroup]['additionalEmails'][responseControl.value] = + responseControl.errors + } + }) + + if ( + registerForm.email && + registerForm.email.errors && + registerForm.email.errors.length > 0 + ) { + hasErrors = true + error[errorGroup]['email'].push({ + value: registerForm.email.value, + errors: registerForm.email.errors, + }) + } + + return hasErrors ? error : null + } + + public setFormGroupPasswordErrors( + registerForm: RegisterForm, + errorGroup: string + ) { + let hasErrors = false + const error = {} + error[errorGroup] = { + password: [], + passwordConfirm: [], + } + + if ( + registerForm.password && + registerForm.password.errors && + registerForm.password.errors.length > 0 + ) { + hasErrors = true + error[errorGroup]['password'].push({ + value: registerForm.email.value, + errors: registerForm.email.errors, + }) + } + if ( + registerForm.passwordConfirm && + registerForm.passwordConfirm.errors && + registerForm.passwordConfirm.errors.length > 0 + ) { + hasErrors = true + error[errorGroup]['passwordConfirm'].push({ + value: registerForm.passwordConfirm.value, + errors: registerForm.passwordConfirm.errors, + }) + } + + return hasErrors ? error : null + } + } +} diff --git a/src/app/core/register2/register2.form-adapter.ts b/src/app/core/register2/register2.form-adapter.ts new file mode 100644 index 0000000000..2e8d82b4a1 --- /dev/null +++ b/src/app/core/register2/register2.form-adapter.ts @@ -0,0 +1,136 @@ +import { UntypedFormGroup } from '@angular/forms' +import { Constructor } from 'src/app/types' +import { Value, Visibility } from 'src/app/types/common.endpoint' +import { RegisterForm } from 'src/app/types/register.endpoint' + +export function Register2FormAdapterMixin>(base: T) { + return class RegisterFormAdapter extends base { + formGroupToEmailRegisterForm(formGroup: UntypedFormGroup): RegisterForm { + let additionalEmailsValue: Value[] + if (formGroup.controls['additionalEmails']) { + const additionalEmailsControls = ( + formGroup.controls['additionalEmails'] as UntypedFormGroup + ).controls + additionalEmailsValue = Object.keys(additionalEmailsControls) + .filter((name) => additionalEmailsControls[name].value !== '') + .map((name) => { + if (additionalEmailsControls[name].value) { + return { value: additionalEmailsControls[name].value } + } + }) + } + let emailValue + if (formGroup.controls['email']) { + emailValue = formGroup.controls['email'].value + } + + const value: RegisterForm = {} + + if (emailValue) { + value['email'] = { value: emailValue } + } + if (additionalEmailsValue) { + value['emailsAdditional'] = additionalEmailsValue + } + return value + } + + formGroupToNamesRegisterForm(formGroup: UntypedFormGroup): RegisterForm { + return { + givenNames: { value: formGroup.controls['givenNames'].value }, + familyNames: { value: formGroup.controls['familyNames'].value }, + } + } + + formGroupToActivitiesVisibilityForm( + formGroup: UntypedFormGroup + ): RegisterForm { + let activitiesVisibilityDefault: Visibility + if ( + formGroup && + formGroup.controls && + formGroup.controls['activitiesVisibilityDefault'] + ) { + activitiesVisibilityDefault = { + visibility: formGroup.controls['activitiesVisibilityDefault'].value, + } + } + return { activitiesVisibilityDefault } + } + + formGroupToPasswordRegisterForm(formGroup: UntypedFormGroup): RegisterForm { + let password: Value + if (formGroup && formGroup.controls && formGroup.controls['password']) { + password = { value: formGroup.controls['password'].value } + } + let passwordConfirm: Value + if ( + formGroup && + formGroup.controls && + formGroup.controls['passwordConfirm'] + ) { + passwordConfirm = { value: formGroup.controls['passwordConfirm'].value } + } + return { password, passwordConfirm } + } + + formGroupTermsOfUseAndDataProcessedRegisterForm( + formGroup: UntypedFormGroup + ): RegisterForm { + let termsOfUse: Value + let dataProcessed: Value + if (formGroup && formGroup.controls) { + if (formGroup.controls['termsOfUse']) { + termsOfUse = { value: formGroup.controls['termsOfUse'].value } + } + if (formGroup.controls['dataProcessed']) { + dataProcessed = { value: formGroup.controls['dataProcessed'].value } + } + } + return { termsOfUse, dataProcessed } + } + + formGroupToSendOrcidNewsForm(formGroup: UntypedFormGroup) { + let sendOrcidNews: Value + if ( + formGroup && + formGroup.controls && + formGroup.controls['sendOrcidNews'] + ) { + sendOrcidNews = { value: formGroup.controls['sendOrcidNews'].value } + } + return { sendOrcidNews } + } + + formGroupToRecaptchaForm( + formGroup: UntypedFormGroup, + widgetId: number + ): RegisterForm { + const value: RegisterForm = {} + value.grecaptchaWidgetId = { + value: widgetId != null ? widgetId.toString() : null, + } + if (formGroup && formGroup.controls && formGroup.controls['captcha']) { + value.grecaptcha = { value: formGroup.controls['captcha'].value } + } + return value + } + + formGroupToFullRegistrationForm( + StepA: UntypedFormGroup, + StepB: UntypedFormGroup, + StepC: UntypedFormGroup, + StepD: UntypedFormGroup + + ): RegisterForm { + return { + ...StepA.value.personal, + ...StepB.value.password, + ...StepB.value.sendOrcidNews, + ...StepD.value.activitiesVisibilityDefault, + ...StepD.value.termsOfUse, + ...StepD.value.captcha, + } + } + } +} diff --git a/src/app/core/register2/register2.service.ts b/src/app/core/register2/register2.service.ts new file mode 100644 index 0000000000..00c7d05e2c --- /dev/null +++ b/src/app/core/register2/register2.service.ts @@ -0,0 +1,183 @@ +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { UntypedFormGroup } from '@angular/forms' +import { Observable } from 'rxjs' +import { catchError, first, map, retry, switchMap } from 'rxjs/operators' +import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' +import { RequestInfoForm } from 'src/app/types' +import { + DuplicatedName, + RegisterConfirmResponse, + RegisterForm, +} from 'src/app/types/register.endpoint' +import { environment } from 'src/environments/environment' + +import { ERROR_REPORT } from 'src/app/errors' +import { objectToUrlParameters } from '../../constants' +import { ReactivationLocal } from '../../types/reactivation.local' +import { ErrorHandlerService } from '../error-handler/error-handler.service' +import { UserService } from '../user/user.service' +import { Register2BackendValidatorMixin } from './register2.backend-validators' +import { Register2FormAdapterMixin } from './register2.form-adapter' + +// Mixing boiler plate + +class Register2ServiceBase { + constructor( + public _http: HttpClient, + public _errorHandler: ErrorHandlerService + ) {} +} +const _RegisterServiceMixingBase = Register2BackendValidatorMixin( + Register2FormAdapterMixin(Register2ServiceBase) +) + +@Injectable({ + providedIn: 'root', +}) +export class Register2Service extends _RegisterServiceMixingBase { + backendRegistrationForm: RegisterForm + + constructor( + _http: HttpClient, + _errorHandler: ErrorHandlerService, + private _userService: UserService, + private _platform: PlatformInfoService + ) { + super(_http, _errorHandler) + } + + public checkDuplicatedResearcher(names: { + familyNames: string + givenNames: string + }) { + return this._http + .get(environment.API_WEB + `dupicateResearcher.json`, { + params: names, + withCredentials: true, + }) + .pipe( + retry(3), + catchError((error) => this._errorHandler.handleError(error)) + ) + } + + getRegisterForm(): Observable { + return this._http + .get(`${environment.API_WEB}register.json`, { + withCredentials: true, + }) + .pipe( + retry(3), + catchError((error) => this._errorHandler.handleError(error)) + ) + .pipe(map((form) => (this.backendRegistrationForm = form))) + } + + register( + StepA: UntypedFormGroup, + StepB: UntypedFormGroup, + StepC: UntypedFormGroup, + StepD: UntypedFormGroup, + reactivation: ReactivationLocal, + requestInfoForm?: RequestInfoForm, + updateUserService = true + ): Observable { + this.backendRegistrationForm.valNumClient = + this.backendRegistrationForm.valNumServer / 2 + const registerForm = this.formGroupToFullRegistrationForm( + StepA, + StepB, + StepC, + StepD + ) + this.addOauthContext(registerForm, requestInfoForm) + return this._platform.get().pipe( + first(), + switchMap((platform) => { + let url = `${environment.API_WEB}` + if ( + platform.institutional || + platform.queryParameters.linkType === 'shibboleth' + ) { + url += `shibboleth/` + } + if (reactivation.isReactivation) { + url += `reactivationConfirm.json?${objectToUrlParameters( + platform.queryParameters + )}` + registerForm.resetParams = reactivation.reactivationCode + } else { + url += `registerConfirm.json?${objectToUrlParameters( + platform.queryParameters + )}` + } + + const registerFormWithTypeContext = this.addCreationTypeContext( + platform, + registerForm + ) + + return this._http + .post( + url, + Object.assign( + this.backendRegistrationForm, + registerFormWithTypeContext + ) + ) + .pipe( + retry(3), + catchError((error) => + this._errorHandler.handleError(error, ERROR_REPORT.REGISTER) + ), + switchMap((value) => { + return this._userService.refreshUserSession(true, true).pipe( + first(), + map((userStatus) => { + if (!userStatus.loggedIn && !value.errors) { + // sanity check the user should be logged + // sanity check the user should be logged + this._errorHandler.handleError( + new Error('registerSanityIssue'), + ERROR_REPORT.REGISTER + ) + } + return value + }) + ) + }) + ) + }) + ) + } + + addOauthContext( + registerForm: RegisterForm, + requestInfoForm?: RequestInfoForm + ): void { + if (requestInfoForm) { + registerForm.referredBy = { value: requestInfoForm.clientId } + } + } + addCreationTypeContext( + platform: PlatformInfo, + registerForm: RegisterForm + ): RegisterForm { + /// TODO @leomendoza123 depend only on the user session thirty party login data + /// avoid taking data from the the parameters. + if ( + platform.social || + platform.queryParameters.providerId === 'facebook' || + platform.queryParameters.providerId === 'google' + ) { + registerForm.linkType = 'social' + return registerForm + } else if (platform.institutional || platform.queryParameters.providerId) { + registerForm.linkType = 'shibboleth' + return registerForm + } else { + return registerForm + } + } +} diff --git a/src/app/register/pages/register/register.component.ts b/src/app/register/pages/register/register.component.ts index b64834de10..62aa6abb2f 100644 --- a/src/app/register/pages/register/register.component.ts +++ b/src/app/register/pages/register/register.component.ts @@ -12,27 +12,26 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms' import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' import { MatStep } from '@angular/material/stepper' import { Router } from '@angular/router' -import { combineLatest, forkJoin, Observable } from 'rxjs' +import { Observable, combineLatest, forkJoin } from 'rxjs' import { catchError, first, map, switchMap } from 'rxjs/operators' import { IsThisYouComponent } from 'src/app/cdk/is-this-you' import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' import { WINDOW } from 'src/app/cdk/window' import { isRedirectToTheAuthorizationPage } from 'src/app/constants' import { UserService } from 'src/app/core' +import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' import { RegisterService } from 'src/app/core/register/register.service' -import { RequestInfoForm } from 'src/app/types' +import { ERROR_REPORT } from 'src/app/errors' +import { RequestInfoForm, SearchParameters, SearchResults } from 'src/app/types' import { RegisterConfirmResponse, RegisterForm, } from 'src/app/types/register.endpoint' -import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' -import { ERROR_REPORT } from 'src/app/errors' import { UserSession } from 'src/app/types/session.local' import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' -import { ReactivationLocal } from '../../../types/reactivation.local' -import { SearchService } from '../../../core/search/search.service' -import { SearchParameters, SearchResults } from 'src/app/types' import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' +import { SearchService } from '../../../core/search/search.service' +import { ReactivationLocal } from '../../../types/reactivation.local' @Component({ selector: 'app-register', diff --git a/src/app/register2/register-routing.module.ts b/src/app/register2/register-routing.module.ts index 9f62a335f0..7dbd8286d7 100644 --- a/src/app/register2/register-routing.module.ts +++ b/src/app/register2/register-routing.module.ts @@ -1,11 +1,11 @@ import { NgModule } from '@angular/core' -import { Routes, RouterModule } from '@angular/router' -import { RegisterComponent } from './pages/register/register.component' +import { RouterModule, Routes } from '@angular/router' +import { Register2Component } from './pages/register/register2.component' const routes: Routes = [ { path: '', - component: RegisterComponent, + component: Register2Component, }, ] diff --git a/src/app/register2/register.module.ts b/src/app/register2/register.module.ts index 22d137884f..465e046e6a 100644 --- a/src/app/register2/register.module.ts +++ b/src/app/register2/register.module.ts @@ -9,7 +9,6 @@ import { FormVisibilityComponent } from './components/form-visibility/form-visib import { StepAComponent } from './components/step-a/step-a.component' import { StepBComponent } from './components/step-b/step-b.component' import { StepDComponent } from './components/step-d/step-d.component' -import { RegisterComponent } from './pages/register/register.component' import { RegisterRoutingModule } from './register-routing.module' // tslint:disable-next-line: max-line-length import { MatIconModule } from '@angular/material/icon' @@ -33,9 +32,9 @@ import { FormAntiRobotsComponent } from './components/form-anti-robots/form-anti import { FormPersonalAdditionalEmailsComponent } from './components/form-personal-additional-emails/form-personal-additional-emails.component' import { StepCTComponent } from './components/step-c-t/step-c.component' import { TopBarRecordIssuesComponent } from './components/top-bar-record-issues/top-bar-record-issues.component' +import { Register2Component } from './pages/register/register2.component' @NgModule({ declarations: [ - RegisterComponent, StepAComponent, StepBComponent, StepCTComponent, @@ -49,6 +48,7 @@ import { TopBarRecordIssuesComponent } from './components/top-bar-record-issues/ FormAntiRobotsComponent, BackendErrorComponent, TopBarRecordIssuesComponent, + Register2Component ], imports: [ CommonModule, From a5ea8b3dd090abd4ae613fbabbaa821982e9b53b Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Tue, 14 Nov 2023 15:38:00 -0600 Subject: [PATCH 05/21] register2service --- .../pages/register/register.component.html | 64 ---- .../pages/register/register.component.scss | 19 -- .../pages/register/register.component.spec.ts | 54 --- .../pages/register/register.component.ts | 318 ------------------ 4 files changed, 455 deletions(-) delete mode 100644 src/app/register2/pages/register/register.component.html delete mode 100644 src/app/register2/pages/register/register.component.scss delete mode 100644 src/app/register2/pages/register/register.component.spec.ts delete mode 100644 src/app/register2/pages/register/register.component.ts diff --git a/src/app/register2/pages/register/register.component.html b/src/app/register2/pages/register/register.component.html deleted file mode 100644 index 90c64f16b4..0000000000 --- a/src/app/register2/pages/register/register.component.html +++ /dev/null @@ -1,64 +0,0 @@ -
-
-
-
- - - - - - Visibility and terms - - - -
-
-
-
diff --git a/src/app/register2/pages/register/register.component.scss b/src/app/register2/pages/register/register.component.scss deleted file mode 100644 index 44be1dfeba..0000000000 --- a/src/app/register2/pages/register/register.component.scss +++ /dev/null @@ -1,19 +0,0 @@ -:host { - width: 100%; - - ::ng-deep { - .mat-horizontal-stepper-header-container { - display: none; - } - } -} - -mat-vertical-stepper, -mat-horizontal-stepper { - max-width: 100%; - width: 100%; -} - -.max-witdth { - max-width: 580px; -} diff --git a/src/app/register2/pages/register/register.component.spec.ts b/src/app/register2/pages/register/register.component.spec.ts deleted file mode 100644 index 1967801d07..0000000000 --- a/src/app/register2/pages/register/register.component.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing' - -import { RegisterComponent } from './register.component' -import { PlatformInfoService } from '../../../cdk/platform-info' -import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' -import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' -import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' -import { Overlay } from '@angular/cdk/overlay' -import { RouterTestingModule } from '@angular/router/testing' -import { HttpClientTestingModule } from '@angular/common/http/testing' -import { WINDOW_PROVIDERS } from '../../../cdk/window' -import { UntypedFormBuilder } from '@angular/forms' -import { - MatLegacyDialog as MatDialog, - MatLegacyDialogModule as MatDialogModule, -} from '@angular/material/legacy-dialog' -import { RegisterService } from '../../../core/register/register.service' -import { UserService } from '../../../core' -import { SearchService } from '../../../core/search/search.service' - -describe('RegisterComponent', () => { - let component: RegisterComponent - let fixture: ComponentFixture - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, MatDialogModule, RouterTestingModule], - declarations: [RegisterComponent], - providers: [ - WINDOW_PROVIDERS, - UntypedFormBuilder, - RegisterService, - SearchService, - UserService, - PlatformInfoService, - ErrorHandlerService, - SnackbarService, - MatSnackBar, - MatDialog, - Overlay, - ], - }).compileComponents() - }) - - beforeEach(() => { - fixture = TestBed.createComponent(RegisterComponent) - component = fixture.componentInstance - fixture.detectChanges() - }) - - it('should create', () => { - expect(component).toBeTruthy() - }) -}) diff --git a/src/app/register2/pages/register/register.component.ts b/src/app/register2/pages/register/register.component.ts deleted file mode 100644 index 595c6fc206..0000000000 --- a/src/app/register2/pages/register/register.component.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { StepperSelectionEvent } from '@angular/cdk/stepper' -import { - AfterViewInit, - ChangeDetectorRef, - Component, - ElementRef, - Inject, - OnInit, - ViewChild, -} from '@angular/core' -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms' -import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' -import { MatStep } from '@angular/material/stepper' -import { Router } from '@angular/router' -import { Observable, combineLatest, forkJoin } from 'rxjs' -import { catchError, first, map, switchMap } from 'rxjs/operators' -import { IsThisYouComponent } from 'src/app/cdk/is-this-you' -import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' -import { WINDOW } from 'src/app/cdk/window' -import { isRedirectToTheAuthorizationPage } from 'src/app/constants' -import { UserService } from 'src/app/core' -import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' -import { RegisterService } from 'src/app/core/register/register.service' -import { ERROR_REPORT } from 'src/app/errors' -import { RequestInfoForm, SearchResults } from 'src/app/types' -import { - RegisterConfirmResponse, - RegisterForm, -} from 'src/app/types/register.endpoint' -import { UserSession } from 'src/app/types/session.local' -import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' -import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' -import { SearchService } from '../../../core/search/search.service' -import { ReactivationLocal } from '../../../types/reactivation.local' - -@Component({ - selector: 'app-register', - templateUrl: './register.component.html', - styleUrls: ['./register.component.scss'], -}) -export class RegisterComponent implements OnInit, AfterViewInit { - @ViewChild('lastStep') lastStep: MatStep - @ViewChild('stepComponentA', { read: ElementRef }) stepComponentA: ElementRef - @ViewChild('stepComponentB', { read: ElementRef }) stepComponentB: ElementRef - @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef - platform: PlatformInfo - FormGroupStepA: UntypedFormGroup - FormGroupStepB: UntypedFormGroup - FormGroupStepCT: UntypedFormGroup - FormGroupStepC: UntypedFormGroup - isLinear = true - personalData: RegisterForm - backendForm: RegisterForm - loading = false - requestInfoForm: RequestInfoForm | null - thirdPartyAuthData: ThirdPartyAuthData - reactivation = { - isReactivation: false, - reactivationCode: '', - } as ReactivationLocal - - constructor( - private _cdref: ChangeDetectorRef, - private _platformInfo: PlatformInfoService, - private _formBuilder: UntypedFormBuilder, - private _register: RegisterService, - private _dialog: MatDialog, - @Inject(WINDOW) private window: Window, - private _googleTagManagerService: GoogleTagManagerService, - private _user: UserService, - private _router: Router, - private _errorHandler: ErrorHandlerService, - private _userInfo: UserService, - private _searchService: SearchService - ) { - _platformInfo.get().subscribe((platform) => { - this.platform = platform - this.reactivation.isReactivation = this.platform.reactivation - this.reactivation.reactivationCode = this.platform.reactivationCode - }) - } - ngOnInit() { - this._register.getRegisterForm().subscribe() - - this.FormGroupStepA = this._formBuilder.group({ - personal: [''], - }) - this.FormGroupStepB = this._formBuilder.group({ - password: [''], - sendOrcidNews: [''], - }) - this.FormGroupStepCT = this._formBuilder.group({ - activitiesVisibilityDefault: [''], - }) - - this.FormGroupStepC = this._formBuilder.group({ - activitiesVisibilityDefault: [''], - termsOfUse: [''], - captcha: [''], - }) - - combineLatest([this._userInfo.getUserSession(), this._platformInfo.get()]) - .pipe( - first(), - map(([session, platform]) => { - session = session as UserSession - platform = platform as PlatformInfo - - // TODO @leomendoza123 move the handle of social/institutional sessions to the user service - - this.thirdPartyAuthData = session.thirdPartyAuthData - this.requestInfoForm = session.oauthSession - - if (this.thirdPartyAuthData || this.requestInfoForm) { - this.FormGroupStepA = this.prefillRegisterForm( - this.requestInfoForm, - this.thirdPartyAuthData - ) - } - }) - ) - .subscribe() - } - - ngAfterViewInit(): void { - this._cdref.detectChanges() - } - - register() { - this.loading = true - this.lastStep.interacted = true - if ( - this.FormGroupStepA.valid && - this.FormGroupStepB.valid && - this.FormGroupStepC.valid - ) { - this._register - .backendRegisterFormValidate( - this.FormGroupStepA, - this.FormGroupStepB, - this.FormGroupStepC - ) - .pipe( - switchMap((validator: RegisterForm) => { - if (validator.errors.length > 0) { - // At this point any backend error is unexpected - this._errorHandler.handleError( - new Error('registerUnexpectedValidateFail'), - ERROR_REPORT.REGISTER - ) - } - return this._register.register( - this.FormGroupStepA, - this.FormGroupStepB, - this.FormGroupStepC, - this.reactivation, - this.requestInfoForm, - true - ) - }) - ) - .subscribe((response) => { - this.loading = false - if (response.url) { - const analyticsReports: Observable[] = [] - - analyticsReports.push( - this._googleTagManagerService.reportEvent( - 'New-Registration', - this.requestInfoForm || 'Website' - ) - ) - forkJoin(analyticsReports) - .pipe( - catchError((err) => - this._errorHandler.handleError( - err, - ERROR_REPORT.STANDARD_NO_VERBOSE_NO_GA - ) - ) - ) - .subscribe( - () => this.afterRegisterRedirectionHandler(response), - () => this.afterRegisterRedirectionHandler(response) - ) - } else { - this._errorHandler.handleError( - new Error('registerUnexpectedConfirmation'), - ERROR_REPORT.REGISTER - ) - } - }) - } else { - this.loading = false - } - } - - afterRegisterRedirectionHandler(response: RegisterConfirmResponse) { - if (isRedirectToTheAuthorizationPage(response)) { - this.window.location.href = response.url - } else { - if ( - response.url.indexOf('orcid.org/my-orcid') > 0 && - response.url.indexOf('justRegistered') > 0 - ) { - this.window.scrollTo(0, 0) - this._router - .navigate(['/my-orcid'], { - queryParams: { justRegistered: true }, - }) - .then(() => { - this.window.scrollTo(0, 0) - }) - } else { - this.window.location.href = response.url - } - } - } - - openDialog(duplicateRecordsSearchResults: SearchResults): void { - const duplicateRecords = duplicateRecordsSearchResults['expanded-result'] - const dialogParams = { - width: `1078px`, - height: `600px`, - maxWidth: `90vw`, - - data: { - duplicateRecords, - titleLabel: $localize`:@@register.titleLabel:Could this be you?`, - // tslint:disable-next-line: max-line-length - bodyLabel: $localize`:@@register.bodyLabel:We found some accounts with your name, which means you may have already created an ORCID iD using a different email address. Before creating an account, please confirm that none of these records belong to you. Not sure if any of these are you?`, - contactLabel: $localize`:@@register.contactLabel:Contact us.`, - firstNameLabel: $localize`:@@register.firstNameLabel:First Name`, - lastNameLabel: $localize`:@@register.lastNameLabel:Last Name`, - affiliationsLabel: $localize`:@@register.affiliationsLabel:Affiliations`, - dateCreatedLabel: $localize`:@@register.dateCreatedLabel:Date Created`, - viewRecordLabel: $localize`:@@register.viewRecordLabel:View Record`, - signinLabel: $localize`:@@register.signinLabel:I ALREADY HAVE AN ID, GO BACK TO SIGN IN`, - continueLabel: $localize`:@@register.continueLabel:NONE OF THESE ARE ME, CONTINUE WITH REGISTRATION`, - }, - } - - if (this.platform.tabletOrHandset) { - dialogParams['maxWidth'] = '95vw' - dialogParams['maxHeight'] = '95vh' - } - - const dialogRef = this._dialog.open(IsThisYouComponent, dialogParams) - - dialogRef.afterClosed().subscribe((confirmRegistration) => { - if (!confirmRegistration) { - this._router.navigate(['signin']) - } - }) - } - - selectionChange(event: StepperSelectionEvent) { - if (this.platform.columns4 || this.platform.columns8) { - this.focusCurrentStep(event) - } - } - - // Fix to material vertical stepper not focusing current header - // related issue https://github.com/angular/components/issues/8881 - focusCurrentStep(event: StepperSelectionEvent) { - let nextStep: ElementRef - if (event.selectedIndex === 0) { - nextStep = this.stepComponentA - } else if (event.selectedIndex === 1) { - nextStep = this.stepComponentB - } else if (event.selectedIndex === 2) { - nextStep = this.stepComponentC - } - // On mobile scroll the current step component into view - if (this.platform.columns4 || this.platform.columns8) { - setTimeout(() => { - const nativeElementNextStep = nextStep.nativeElement as HTMLElement - nativeElementNextStep.scrollIntoView() - }, 200) - } - } - - /** - * Fills the register form. - * Use the data from the Oauth session send by the Orcid integrator - * or - * Use data coming from a third party institution/social entity - * or - * Use empty values - */ - private prefillRegisterForm( - oauthData: RequestInfoForm, - thirdPartyOauthData: ThirdPartyAuthData - ) { - return this._formBuilder.group({ - personal: [ - { - givenNames: - oauthData?.userGivenNames || - thirdPartyOauthData?.signinData?.firstName || - '', - familyNames: - oauthData?.userFamilyNames || - thirdPartyOauthData?.signinData?.lastName || - '', - emails: { - email: - oauthData?.userEmail || - thirdPartyOauthData?.signinData?.email || - '', - confirmEmail: '', - additionalEmails: { '0': '' }, - }, - }, - ], - }) - } -} From 5ce0da812af194e95ebbeb0d478d990d10569850 Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Fernadez Date: Tue, 14 Nov 2023 15:41:05 -0600 Subject: [PATCH 06/21] terms of use and captcha step part1 --- .../form-anti-robots.component.spec.ts | 20 +++--- .../form-anti-robots.component.ts | 8 +-- .../form-notifications.component.html | 48 +++++---------- .../form-notifications.component.spec.ts | 15 +++-- .../form-notifications.component.ts | 14 +++-- .../form-password.component.spec.ts | 18 +++--- .../form-password/form-password.component.ts | 4 +- .../form-personal.component.spec.ts | 24 +++----- .../form-personal/form-personal.component.ts | 4 +- .../form-terms/form-terms.component.html | 2 +- .../form-terms/form-terms.component.spec.ts | 16 ++--- .../form-terms/form-terms.component.ts | 12 ++-- .../form-visibility.component.spec.ts | 16 ++--- .../form-visibility.component.ts | 4 +- .../components/step-b/step-b.component.html | 38 +++++++----- .../components/step-c-t/step-c.component.html | 3 +- .../components/step-d/step-d.component.html | 35 ++++++----- .../pages/register/register2.component.html | 56 ++++------------- .../pages/register/register2.component.ts | 61 +++++++------------ 19 files changed, 176 insertions(+), 222 deletions(-) diff --git a/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts b/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts index eb5403a67e..ee7286cf9d 100644 --- a/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts +++ b/src/app/register2/components/form-anti-robots/form-anti-robots.component.spec.ts @@ -1,17 +1,17 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { FormAntiRobotsComponent } from './form-anti-robots.component' -import { WINDOW_PROVIDERS } from '../../../cdk/window' -import { RegisterService } from '../../../core/register/register.service' -import { PlatformInfoService } from '../../../cdk/platform-info' -import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' -import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' -import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' -import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' import { Overlay } from '@angular/cdk/overlay' import { HttpClientTestingModule } from '@angular/common/http/testing' -import { RouterTestingModule } from '@angular/router/testing' import { ErrorStateMatcher } from '@angular/material/core' +import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { RouterTestingModule } from '@angular/router/testing' +import { PlatformInfoService } from '../../../cdk/platform-info' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { Register2Service } from '../../../core/register2/register2.service' +import { FormAntiRobotsComponent } from './form-anti-robots.component' describe('FormAntiRobotsComponent', () => { let component: FormAntiRobotsComponent @@ -23,7 +23,7 @@ describe('FormAntiRobotsComponent', () => { declarations: [FormAntiRobotsComponent], providers: [ WINDOW_PROVIDERS, - RegisterService, + Register2Service, ErrorStateMatcher, PlatformInfoService, ErrorHandlerService, diff --git a/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts b/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts index 959ba4876b..8e2db1ea13 100644 --- a/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts +++ b/src/app/register2/components/form-anti-robots/form-anti-robots.component.ts @@ -1,15 +1,15 @@ import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' import { - UntypedFormControl, - UntypedFormGroup, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, ValidatorFn, Validators, } from '@angular/forms' import { ErrorStateMatcher } from '@angular/material/core' import { merge, Subject } from 'rxjs' -import { RegisterService } from 'src/app/core/register/register.service' +import { Register2Service } from 'src/app/core/register2/register2.service' import { BaseForm } from '../BaseForm' @@ -48,7 +48,7 @@ export class FormAntiRobotsComponent } constructor( - private _register: RegisterService, + private _register: Register2Service, private _errorStateMatcher: ErrorStateMatcher ) { super() diff --git a/src/app/register2/components/form-notifications/form-notifications.component.html b/src/app/register2/components/form-notifications/form-notifications.component.html index d518331c03..a1ee26a6f7 100644 --- a/src/app/register2/components/form-notifications/form-notifications.component.html +++ b/src/app/register2/components/form-notifications/form-notifications.component.html @@ -1,35 +1,19 @@ -

- Notification settings -

- -

- ORCID sends email notifications about items related to your account, security, - and privacy, including requests from ORCID member organizations for permission - to update your record, and changes made to your record by those organizations. -

- -

- You can also choose to receive emails from us about new features and tips for - making the most of your ORCID record. -

- - +
+

+ Tips & features email +

+

+ We occasionally send out an email with information on new features and tips + for getting the best out of your ORCID record. +

Please send me quarterly emails about new ORCID features and - tips. - To receive these emails, you will also need to verify your primary email - address. + class="margin-bottom-16" + color="primary" + > + +
I’d like to receive the ORCID tips & features email +
- - -

- After you've registered, you can change your notification settings at any time - in the account settings section of your ORCID record. -

+
diff --git a/src/app/register2/components/form-notifications/form-notifications.component.spec.ts b/src/app/register2/components/form-notifications/form-notifications.component.spec.ts index 74d5498538..ddf0a6fa7e 100644 --- a/src/app/register2/components/form-notifications/form-notifications.component.spec.ts +++ b/src/app/register2/components/form-notifications/form-notifications.component.spec.ts @@ -1,19 +1,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { FormNotificationsComponent } from './form-notifications.component' -import { RegisterService } from '../../../core/register/register.service' +import { Overlay } from '@angular/cdk/overlay' import { HttpClientTestingModule } from '@angular/common/http/testing' import { - MatLegacyDialog as MatDialog, - MatLegacyDialogModule as MatDialogModule, + MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' import { RouterTestingModule } from '@angular/router/testing' import { PlatformInfoService } from '../../../cdk/platform-info' -import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' -import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' -import { Overlay } from '@angular/cdk/overlay' import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { Register2Service } from '../../../core/register2/register2.service' +import { FormNotificationsComponent } from './form-notifications.component' describe('FormNotificationsComponent', () => { let component: FormNotificationsComponent @@ -25,7 +24,7 @@ describe('FormNotificationsComponent', () => { declarations: [FormNotificationsComponent], providers: [ WINDOW_PROVIDERS, - RegisterService, + Register2Service, PlatformInfoService, ErrorHandlerService, SnackbarService, diff --git a/src/app/register2/components/form-notifications/form-notifications.component.ts b/src/app/register2/components/form-notifications/form-notifications.component.ts index 11982529df..edbfad6e43 100644 --- a/src/app/register2/components/form-notifications/form-notifications.component.ts +++ b/src/app/register2/components/form-notifications/form-notifications.component.ts @@ -1,19 +1,23 @@ import { Component, forwardRef, OnInit } from '@angular/core' import { - UntypedFormControl, - UntypedFormGroup, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, Validators, } from '@angular/forms' -import { RegisterService } from 'src/app/core/register/register.service' +import { Register2Service } from 'src/app/core/register2/register2.service' import { BaseForm } from '../BaseForm' @Component({ selector: 'app-form-notifications', templateUrl: './form-notifications.component.html', - styleUrls: ['./form-notifications.component.scss'], + styleUrls: [ + './form-notifications.component.scss', + '../register2.style.scss', + '../register2.scss-theme.scss', + ], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -28,7 +32,7 @@ import { BaseForm } from '../BaseForm' ], }) export class FormNotificationsComponent extends BaseForm implements OnInit { - constructor(private _register: RegisterService) { + constructor(private _register: Register2Service) { super() } ngOnInit() { diff --git a/src/app/register2/components/form-password/form-password.component.spec.ts b/src/app/register2/components/form-password/form-password.component.spec.ts index 32f79f6159..8fb75fe8d3 100644 --- a/src/app/register2/components/form-password/form-password.component.spec.ts +++ b/src/app/register2/components/form-password/form-password.component.spec.ts @@ -1,21 +1,21 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { FormPasswordComponent } from './form-password.component' +import { Overlay } from '@angular/cdk/overlay' import { HttpClientTestingModule } from '@angular/common/http/testing' -import { RouterTestingModule } from '@angular/router/testing' import { MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialog as MatDialog, MatLegacyDialogRef as MatDialogRef, } from '@angular/material/legacy-dialog' -import { WINDOW_PROVIDERS } from '../../../cdk/window' -import { PlatformInfoService } from '../../../cdk/platform-info' -import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' -import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' -import { Overlay } from '@angular/cdk/overlay' -import { RegisterService } from '../../../core/register/register.service' +import { RouterTestingModule } from '@angular/router/testing' +import { PlatformInfoService } from '../../../cdk/platform-info' import { MdePopoverModule } from '../../../cdk/popover' +import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { Register2Service } from '../../../core/register2/register2.service' +import { FormPasswordComponent } from './form-password.component' describe('FormPasswordComponent', () => { let component: FormPasswordComponent @@ -29,7 +29,7 @@ describe('FormPasswordComponent', () => { { provide: MatDialogRef, useValue: {} }, { provide: MAT_DIALOG_DATA, useValue: {} }, WINDOW_PROVIDERS, - RegisterService, + Register2Service, PlatformInfoService, ErrorHandlerService, SnackbarService, diff --git a/src/app/register2/components/form-password/form-password.component.ts b/src/app/register2/components/form-password/form-password.component.ts index 32d5d53dcc..34e173d34f 100644 --- a/src/app/register2/components/form-password/form-password.component.ts +++ b/src/app/register2/components/form-password/form-password.component.ts @@ -8,7 +8,7 @@ import { Validators, } from '@angular/forms' import { HAS_LETTER_OR_SYMBOL, HAS_NUMBER } from 'src/app/constants' -import { RegisterService } from 'src/app/core/register/register.service' +import { Register2Service } from 'src/app/core/register2/register2.service' import { RegisterForm } from 'src/app/types/register.endpoint' import { OrcidValidators } from 'src/app/validators' @@ -46,7 +46,7 @@ export class FormPasswordComponent extends BaseForm implements OnInit { hasNumberPattern = HAS_NUMBER hasLetterOrSymbolPattern = HAS_LETTER_OR_SYMBOL @Input() personalData: RegisterForm - constructor(private _register: RegisterService) { + constructor(private _register: Register2Service) { super() } ngOnInit() { diff --git a/src/app/register2/components/form-personal/form-personal.component.spec.ts b/src/app/register2/components/form-personal/form-personal.component.spec.ts index b34b3e95fe..570b9eca44 100644 --- a/src/app/register2/components/form-personal/form-personal.component.spec.ts +++ b/src/app/register2/components/form-personal/form-personal.component.spec.ts @@ -1,24 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { FormPersonalComponent } from './form-personal.component' +import { Overlay } from '@angular/cdk/overlay' import { HttpClientTestingModule } from '@angular/common/http/testing' -import { RouterTestingModule } from '@angular/router/testing' import { - MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, - MatLegacyDialog as MatDialog, - MatLegacyDialogRef as MatDialogRef, + MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' -import { WINDOW_PROVIDERS } from '../../../cdk/window' -import { FormBuilder } from '@angular/forms' -import { RecordWorksService } from '../../../core/record-works/record-works.service' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { RouterTestingModule } from '@angular/router/testing' import { PlatformInfoService } from '../../../cdk/platform-info' -import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { MdePopoverModule } from '../../../cdk/popover' import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' -import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' -import { Overlay } from '@angular/cdk/overlay' -import { RegisterService } from '../../../core/register/register.service' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' import { ReactivationService } from '../../../core/reactivation/reactivation.service' -import { MdePopoverModule } from '../../../cdk/popover' +import { Register2Service } from '../../../core/register2/register2.service' +import { FormPersonalComponent } from './form-personal.component' describe('FormPersonalComponent', () => { let component: FormPersonalComponent @@ -31,7 +27,7 @@ describe('FormPersonalComponent', () => { providers: [ WINDOW_PROVIDERS, ReactivationService, - RegisterService, + Register2Service, PlatformInfoService, ErrorHandlerService, SnackbarService, diff --git a/src/app/register2/components/form-personal/form-personal.component.ts b/src/app/register2/components/form-personal/form-personal.component.ts index d1b5a8839c..e79f03eaf1 100644 --- a/src/app/register2/components/form-personal/form-personal.component.ts +++ b/src/app/register2/components/form-personal/form-personal.component.ts @@ -15,7 +15,7 @@ import { ValidatorFn, Validators, } from '@angular/forms' -import { RegisterService } from 'src/app/core/register/register.service' +import { Register2Service } from 'src/app/core/register2/register2.service' import { OrcidValidators } from 'src/app/validators' import { first } from 'rxjs/operators' @@ -51,7 +51,7 @@ export class FormPersonalComponent labelConfirmEmail = $localize`:@@register.confirmEmail:Confirm primary email` labelNameYouMostCommonly = $localize`:@@register.labelNameYouMostMost:The name you most commonly go by` constructor( - private _register: RegisterService, + private _register: Register2Service, private _reactivationService: ReactivationService ) { super() diff --git a/src/app/register2/components/form-terms/form-terms.component.html b/src/app/register2/components/form-terms/form-terms.component.html index 5745d6534b..78e3d503a0 100644 --- a/src/app/register2/components/form-terms/form-terms.component.html +++ b/src/app/register2/components/form-terms/form-terms.component.html @@ -1,4 +1,4 @@ -

Terms of Use

+

Terms of Use

{ let component: FormTermsComponent @@ -22,7 +22,7 @@ describe('FormTermsComponent', () => { declarations: [FormTermsComponent], providers: [ WINDOW_PROVIDERS, - RegisterService, + Register2Service, PlatformInfoService, ErrorHandlerService, SnackbarService, diff --git a/src/app/register2/components/form-terms/form-terms.component.ts b/src/app/register2/components/form-terms/form-terms.component.ts index f0de5ce315..f05c57c6fc 100644 --- a/src/app/register2/components/form-terms/form-terms.component.ts +++ b/src/app/register2/components/form-terms/form-terms.component.ts @@ -1,13 +1,13 @@ import { Component, DoCheck, forwardRef, OnInit } from '@angular/core' import { - UntypedFormControl, - UntypedFormGroup, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, + UntypedFormControl, + UntypedFormGroup, Validators, } from '@angular/forms' import { ErrorStateMatcher } from '@angular/material/core' -import { RegisterService } from 'src/app/core/register/register.service' +import { Register2Service } from 'src/app/core/register2/register2.service' import { environment } from 'src/environments/environment' import { BaseForm } from '../BaseForm' @@ -15,7 +15,9 @@ import { BaseForm } from '../BaseForm' @Component({ selector: 'app-form-terms', templateUrl: './form-terms.component.html', - styleUrls: ['./form-terms.component.scss'], + styleUrls: ['./form-terms.component.scss', + '../register2.style.scss', + '../register2.scss-theme.scss',], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -34,7 +36,7 @@ import { BaseForm } from '../BaseForm' export class FormTermsComponent extends BaseForm implements OnInit, DoCheck { environment = environment constructor( - private _register: RegisterService, + private _register: Register2Service, private _errorStateMatcher: ErrorStateMatcher ) { super() diff --git a/src/app/register2/components/form-visibility/form-visibility.component.spec.ts b/src/app/register2/components/form-visibility/form-visibility.component.spec.ts index 387ddb0103..4e0ac5bf54 100644 --- a/src/app/register2/components/form-visibility/form-visibility.component.spec.ts +++ b/src/app/register2/components/form-visibility/form-visibility.component.spec.ts @@ -1,16 +1,16 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { FormVisibilityComponent } from './form-visibility.component' +import { Overlay } from '@angular/cdk/overlay' import { HttpClientTestingModule } from '@angular/common/http/testing' -import { RouterTestingModule } from '@angular/router/testing' import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' -import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' +import { RouterTestingModule } from '@angular/router/testing' import { PlatformInfoService } from '../../../cdk/platform-info' -import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' import { SnackbarService } from '../../../cdk/snackbar/snackbar.service' -import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar' -import { Overlay } from '@angular/cdk/overlay' -import { RegisterService } from '../../../core/register/register.service' +import { WINDOW_PROVIDERS } from '../../../cdk/window' +import { ErrorHandlerService } from '../../../core/error-handler/error-handler.service' +import { Register2Service } from '../../../core/register2/register2.service' +import { FormVisibilityComponent } from './form-visibility.component' describe('FormVisibilityComponent', () => { let component: FormVisibilityComponent @@ -22,7 +22,7 @@ describe('FormVisibilityComponent', () => { declarations: [FormVisibilityComponent], providers: [ WINDOW_PROVIDERS, - RegisterService, + Register2Service, PlatformInfoService, ErrorHandlerService, SnackbarService, diff --git a/src/app/register2/components/form-visibility/form-visibility.component.ts b/src/app/register2/components/form-visibility/form-visibility.component.ts index f2ef07dcbb..e29daa388b 100644 --- a/src/app/register2/components/form-visibility/form-visibility.component.ts +++ b/src/app/register2/components/form-visibility/form-visibility.component.ts @@ -8,7 +8,7 @@ import { } from '@angular/forms' import { ErrorStateMatcher } from '@angular/material/core' import { VISIBILITY_OPTIONS } from 'src/app/constants' -import { RegisterService } from 'src/app/core/register/register.service' +import { Register2Service } from 'src/app/core/register2/register2.service' import { BaseForm } from '../BaseForm' @@ -42,7 +42,7 @@ export class FormVisibilityComponent errorState = false activitiesVisibilityDefault = new UntypedFormControl('', Validators.required) constructor( - private _register: RegisterService, + private _register: Register2Service, private _errorStateMatcher: ErrorStateMatcher ) { super() diff --git a/src/app/register2/components/step-b/step-b.component.html b/src/app/register2/components/step-b/step-b.component.html index e12ce35510..526d75477e 100644 --- a/src/app/register2/components/step-b/step-b.component.html +++ b/src/app/register2/components/step-b/step-b.component.html @@ -32,28 +32,36 @@ formControlName="password" [personalData]="personalData" > -
- + Previous Step +
diff --git a/src/app/register2/components/step-c-t/step-c.component.html b/src/app/register2/components/step-c-t/step-c.component.html index d6a3f0da49..0c848e2b00 100644 --- a/src/app/register2/components/step-c-t/step-c.component.html +++ b/src/app/register2/components/step-c-t/step-c.component.html @@ -28,7 +28,7 @@
This is step 3 of 3This is step 3 of 4 @@ -45,6 +45,7 @@ mat-raised-button color="primary" [disabled]="loading" + matStepperNext > +
+ orcid logo +
This is step 3 of 3This is step 4 of 4
- +
- + + Previous Step +
diff --git a/src/app/register2/pages/register/register2.component.html b/src/app/register2/pages/register/register2.component.html index 670055304a..9961699a70 100644 --- a/src/app/register2/pages/register/register2.component.html +++ b/src/app/register2/pages/register/register2.component.html @@ -2,12 +2,12 @@
- - + @@ -30,62 +30,30 @@ [personalData]="personalData" [reactivation]="reactivation" > - - + FormGroupStepD + Visibility and terms - + > - - - - - - Personal data - - - - Security and notifications - - - + Visibility and terms - + >
diff --git a/src/app/register2/pages/register/register2.component.ts b/src/app/register2/pages/register/register2.component.ts index 46fc6822a3..3612777a57 100644 --- a/src/app/register2/pages/register/register2.component.ts +++ b/src/app/register2/pages/register/register2.component.ts @@ -12,27 +12,26 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms' import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog' import { MatStep } from '@angular/material/stepper' import { Router } from '@angular/router' -import { combineLatest, forkJoin, Observable } from 'rxjs' +import { Observable, combineLatest, forkJoin } from 'rxjs' import { catchError, first, map, switchMap } from 'rxjs/operators' import { IsThisYouComponent } from 'src/app/cdk/is-this-you' import { PlatformInfo, PlatformInfoService } from 'src/app/cdk/platform-info' import { WINDOW } from 'src/app/cdk/window' import { isRedirectToTheAuthorizationPage } from 'src/app/constants' import { UserService } from 'src/app/core' -import { RegisterService } from 'src/app/core/register/register.service' -import { RequestInfoForm } from 'src/app/types' +import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' +import { Register2Service } from 'src/app/core/register2/register2.service' +import { ERROR_REPORT } from 'src/app/errors' +import { RequestInfoForm, SearchResults } from 'src/app/types' import { RegisterConfirmResponse, RegisterForm, } from 'src/app/types/register.endpoint' -import { ErrorHandlerService } from 'src/app/core/error-handler/error-handler.service' -import { ERROR_REPORT } from 'src/app/errors' import { UserSession } from 'src/app/types/session.local' import { ThirdPartyAuthData } from 'src/app/types/sign-in-data.endpoint' -import { ReactivationLocal } from '../../../types/reactivation.local' -import { SearchService } from '../../../core/search/search.service' -import { SearchParameters, SearchResults } from 'src/app/types' import { GoogleTagManagerService } from '../../../core/google-tag-manager/google-tag-manager.service' +import { SearchService } from '../../../core/search/search.service' +import { ReactivationLocal } from '../../../types/reactivation.local' @Component({ selector: 'app-register-2', @@ -44,10 +43,14 @@ export class Register2Component implements OnInit, AfterViewInit { @ViewChild('stepComponentA', { read: ElementRef }) stepComponentA: ElementRef @ViewChild('stepComponentB', { read: ElementRef }) stepComponentB: ElementRef @ViewChild('stepComponentC', { read: ElementRef }) stepComponentC: ElementRef + @ViewChild('stepComponentD', { read: ElementRef }) stepComponentD: ElementRef + platform: PlatformInfo FormGroupStepA: UntypedFormGroup FormGroupStepB: UntypedFormGroup FormGroupStepC: UntypedFormGroup + FormGroupStepD: UntypedFormGroup + isLinear = true personalData: RegisterForm backendForm: RegisterForm @@ -63,7 +66,7 @@ export class Register2Component implements OnInit, AfterViewInit { private _cdref: ChangeDetectorRef, private _platformInfo: PlatformInfoService, private _formBuilder: UntypedFormBuilder, - private _register: RegisterService, + private _register: Register2Service, private _dialog: MatDialog, @Inject(WINDOW) private window: Window, private _googleTagManagerService: GoogleTagManagerService, @@ -87,10 +90,12 @@ export class Register2Component implements OnInit, AfterViewInit { }) this.FormGroupStepB = this._formBuilder.group({ password: [''], - sendOrcidNews: [''], }) this.FormGroupStepC = this._formBuilder.group({ activitiesVisibilityDefault: [''], + }) + this.FormGroupStepD = this._formBuilder.group({ + sendOrcidNews: [''], termsOfUse: [''], captcha: [''], }) @@ -128,13 +133,15 @@ export class Register2Component implements OnInit, AfterViewInit { if ( this.FormGroupStepA.valid && this.FormGroupStepB.valid && - this.FormGroupStepC.valid + this.FormGroupStepC.valid && + this.FormGroupStepD.valid ) { this._register .backendRegisterFormValidate( this.FormGroupStepA, this.FormGroupStepB, - this.FormGroupStepC + this.FormGroupStepC, + this.FormGroupStepD ) .pipe( switchMap((validator: RegisterForm) => { @@ -149,9 +156,9 @@ export class Register2Component implements OnInit, AfterViewInit { this.FormGroupStepA, this.FormGroupStepB, this.FormGroupStepC, + this.FormGroupStepD, this.reactivation, this.requestInfoForm, - true ) }) ) @@ -213,28 +220,6 @@ export class Register2Component implements OnInit, AfterViewInit { } } - afterStepASubmitted() { - // Update the personal data object is required after submit since is an input for StepB - - if (!this.reactivation?.isReactivation) { - if (this.FormGroupStepA.valid) { - this.personalData = this.FormGroupStepA.value.personal - const searchValue = - this.personalData.familyNames.value + - ' ' + - this.personalData.givenNames.value - const searchParams: SearchParameters = {} - searchParams.searchQuery = searchValue - - this._searchService.search(searchParams).subscribe((value) => { - if (value['num-found'] > 0) { - this.openDialog(value) - } - }) - } - } - } - openDialog(duplicateRecordsSearchResults: SearchResults): void { const duplicateRecords = duplicateRecordsSearchResults['expanded-result'] const dialogParams = { @@ -273,9 +258,6 @@ export class Register2Component implements OnInit, AfterViewInit { } selectionChange(event: StepperSelectionEvent) { - if (event.previouslySelectedIndex === 0) { - this.afterStepASubmitted() - } if (this.platform.columns4 || this.platform.columns8) { this.focusCurrentStep(event) } @@ -284,6 +266,7 @@ export class Register2Component implements OnInit, AfterViewInit { // Fix to material vertical stepper not focusing current header // related issue https://github.com/angular/components/issues/8881 focusCurrentStep(event: StepperSelectionEvent) { + console.log('event', event) let nextStep: ElementRef if (event.selectedIndex === 0) { nextStep = this.stepComponentA @@ -291,6 +274,8 @@ export class Register2Component implements OnInit, AfterViewInit { nextStep = this.stepComponentB } else if (event.selectedIndex === 2) { nextStep = this.stepComponentC + } else if (event.selectedIndex === 3) { + nextStep = this.stepComponentD } // On mobile scroll the current step component into view if (this.platform.columns4 || this.platform.columns8) { From 20ac6f584898f77e345350c21c6f310dac8a6657 Mon Sep 17 00:00:00 2001 From: Leonardo Mendoza Date: Wed, 15 Nov 2023 13:38:46 -0600 Subject: [PATCH 07/21] Add emial already exists --- src/app/core/oauth/oauth.service.ts | 2 +- src/app/core/register2/register2.service.ts | 5 ++ .../form-personal.component.html | 68 +++++++++++++++++-- .../form-personal/form-personal.component.ts | 48 ++++++++++++- .../components/register2.scss-theme.scss | 22 +++++- .../register2/components/register2.style.scss | 50 +++++++++++++- .../components/step-a/step-a.component.html | 8 ++- .../components/step-a/step-a.component.ts | 5 ++ .../components/step-b/step-b.component.html | 8 +-- .../components/step-c-t/step-c.component.html | 8 +-- .../components/step-d/step-d.component.html | 8 +-- .../pages/register/register2.component.html | 4 +- src/app/types/register.email-category.ts | 5 ++ src/app/types/register.endpoint.ts | 1 + src/assets/scss/material.light-theme.scss | 1 + src/assets/scss/material.palettes.scss | 2 + src/assets/vectors/personal-email-icon.svg | 5 ++ .../vectors/professional-email-icon.svg | 4 ++ src/index.html | 5 +- 19 files changed, 224 insertions(+), 35 deletions(-) create mode 100644 src/app/types/register.email-category.ts create mode 100644 src/assets/vectors/personal-email-icon.svg create mode 100644 src/assets/vectors/professional-email-icon.svg diff --git a/src/app/core/oauth/oauth.service.ts b/src/app/core/oauth/oauth.service.ts index 0d01ef65ad..d374d904e5 100644 --- a/src/app/core/oauth/oauth.service.ts +++ b/src/app/core/oauth/oauth.service.ts @@ -96,7 +96,7 @@ export class OauthService { .post( environment.BASE_URL + 'oauth/custom/authorize.json', value, - { headers: this.headers } + { headers: this.headers, withCredentials: true } ) .pipe( retry(3), diff --git a/src/app/core/register2/register2.service.ts b/src/app/core/register2/register2.service.ts index 00c7d05e2c..95a5c55a8f 100644 --- a/src/app/core/register2/register2.service.ts +++ b/src/app/core/register2/register2.service.ts @@ -19,6 +19,7 @@ import { ErrorHandlerService } from '../error-handler/error-handler.service' import { UserService } from '../user/user.service' import { Register2BackendValidatorMixin } from './register2.backend-validators' import { Register2FormAdapterMixin } from './register2.form-adapter' +import { EmailCategoryEndpoint } from 'src/app/types/register.email-category' // Mixing boiler plate @@ -74,6 +75,10 @@ export class Register2Service extends _RegisterServiceMixingBase { .pipe(map((form) => (this.backendRegistrationForm = form))) } + getEmailCategory(email: string): Observable { + return this._http.get(`${environment.API_WEB}email-domain/find-category?domain=${email}`) + } + register( StepA: UntypedFormGroup, StepB: UntypedFormGroup, diff --git a/src/app/register2/components/form-personal/form-personal.component.html b/src/app/register2/components/form-personal/form-personal.component.html index 34153e91b4..f2a156373c 100644 --- a/src/app/register2/components/form-personal/form-personal.component.html +++ b/src/app/register2/components/form-personal/form-personal.component.html @@ -29,7 +29,7 @@

Your names

- + Invalid name characters or format @@ -46,11 +46,11 @@

Your email addres
Email - + @@ -63,13 +63,69 @@

Your email addres Invalid email format - +

+ +
+
+ +
+
+
+

This looks like a personal email

+
+ Add a professional email as backup so we can better recommend affiliations and other related data to you.
+
+
+
+ + +
+
+ +
+
+
+

This looks like a professional email

> +
+
+ We recommend adding a personal email as backup so you always have access to your ORCID account if you change jobs or roles. +
+
+
+
+ + + +
+
+ error +
+
+
+

+ The email address {{emails.get('email').value}} is associated with an existing ORCID record. +

+ +
+
+
+ @@ -79,7 +135,7 @@

Your email addres + [attr.placeholder]="labelConfirmEmail" [disabled]="this.emails.hasError('backendError', 'email') || !emailFormTouched || form.hasError('required', 'emails.email')" /> @@ -122,4 +178,4 @@

Your email addres target="orcid.frontend.register.help.more_info.link.text" i18n="@@register.moreInfoOnNames">More information on names - \ No newline at end of file + diff --git a/src/app/register2/components/form-personal/form-personal.component.ts b/src/app/register2/components/form-personal/form-personal.component.ts index e79f03eaf1..5098ee4f24 100644 --- a/src/app/register2/components/form-personal/form-personal.component.ts +++ b/src/app/register2/components/form-personal/form-personal.component.ts @@ -8,8 +8,11 @@ import { ViewChild, } from '@angular/core' import { + FormControl, + FormGroupDirective, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, + NgForm, UntypedFormControl, UntypedFormGroup, ValidatorFn, @@ -18,10 +21,20 @@ import { import { Register2Service } from 'src/app/core/register2/register2.service' import { OrcidValidators } from 'src/app/validators' -import { first } from 'rxjs/operators' +import { debounce, debounceTime, filter, first, startWith, switchMap } from 'rxjs/operators' import { ReactivationService } from '../../../core/reactivation/reactivation.service' import { ReactivationLocal } from '../../../types/reactivation.local' import { BaseForm } from '../BaseForm' +import { ErrorStateMatcher } from '@angular/material/core' +export class MyErrorStateMatcher implements ErrorStateMatcher { + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + + // !((this.emails.hasError('backendError', 'email') && !nextButtonWasClicked)) + console.log(control) + const isSubmitted = form && form.submitted; + return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted)); + } +} @Component({ selector: 'app-form-personal', @@ -41,15 +54,24 @@ import { BaseForm } from '../BaseForm' }, ], }) + + + export class FormPersonalComponent extends BaseForm implements OnInit, AfterViewInit { + matcher = new MyErrorStateMatcher; + @Input() nextButtonWasClicked: boolean @Input() reactivation: ReactivationLocal @ViewChild('firstInput') firstInput: ElementRef labelInfoAboutName = $localize`:@@register.ariaLabelInfo:info about names` labelClose = $localize`:@@register.ariaLabelClose:close` labelConfirmEmail = $localize`:@@register.confirmEmail:Confirm primary email` labelNameYouMostCommonly = $localize`:@@register.labelNameYouMostMost:The name you most commonly go by` + labelFamilyNamePlaceholder = $localize`:@@register.familyNamePlaceholder:Your family name or surname + ` + professionalEmail: boolean + personalEmail: boolean constructor( private _register: Register2Service, private _reactivationService: ReactivationService @@ -87,6 +109,22 @@ export class FormPersonalComponent } ) + this.emails.controls['email'].valueChanges.pipe(debounceTime(1000),filter( () => + !this.emails.controls['email'].errors + ), switchMap( + (value) => { + const emailDomain = value.split('@')[1] + return this._register.getEmailCategory(emailDomain) + } + )).subscribe(value => { + console.log(value) + this.professionalEmail = value.category === 'PROFESSIONAL' + this.personalEmail = value.category === 'PERSONAL' + + }) + + + if (!this.reactivation?.isReactivation) { this.emails.addControl( 'confirmEmail', @@ -96,6 +134,8 @@ export class FormPersonalComponent ) } + + this.form = new UntypedFormGroup({ givenNames: new UntypedFormControl('', { validators: [Validators.required, OrcidValidators.illegalName], @@ -124,8 +164,10 @@ export class FormPersonalComponent // Timeout used to get focus on the first input after the first step loads setTimeout(() => { this.firstInput.nativeElement.focus() - }, 100) - } + }), 100 + } + + allEmailsAreUnique(): ValidatorFn { return (formGroup: UntypedFormGroup) => { diff --git a/src/app/register2/components/register2.scss-theme.scss b/src/app/register2/components/register2.scss-theme.scss index 9342ce246f..3b46d02f8b 100644 --- a/src/app/register2/components/register2.scss-theme.scss +++ b/src/app/register2/components/register2.scss-theme.scss @@ -8,9 +8,15 @@ $foreground: map-get($theme, foreground); $background: map-get($theme, background); + .step-actions { + border-color: mat.get-color-from-palette($background, + ui-background-light); + } + ::ng-deep .valid { color: mat.get-color-from-palette($accent, 900); } + .error { color: map-get($foreground, 'state-warning-dark'); } @@ -19,6 +25,20 @@ color: map-get($foreground, 'text-dark-mid'); } + + .info { + background-color: mat.get-color-from-palette($background, state-notice-lightest); + border-color: mat.get-color-from-palette($foreground, + 'state-notice-dark' + ) !important; + + mat-icon { + color: mat.get-color-from-palette($foreground, + 'state-notice-dark' + ) !important; + } + } + } -@include theme($orcid-app-theme); +@include theme($orcid-app-theme); \ No newline at end of file diff --git a/src/app/register2/components/register2.style.scss b/src/app/register2/components/register2.style.scss index bfd259e498..cb82894202 100644 --- a/src/app/register2/components/register2.style.scss +++ b/src/app/register2/components/register2.style.scss @@ -3,13 +3,18 @@ a { font-weight: normal; } +h2 { + margin-top: 14px; + margin-bottom: 14px; +} + mat-card-title img { margin-bottom: 32px; } .input-container { - padding-bottom: 24px; + padding-bottom: 20px; } mat-label.orc-font-small-print { @@ -20,6 +25,11 @@ mat-label.orc-font-small-print { } } +.step-actions { + border-top: 1px solid; + padding-top: 24px; +} + :host ::ng-deep { mat-error { @@ -80,10 +90,46 @@ mat-label.orc-font-small-print { } +.orc-font-heading-small { + font-style: normal; + font-weight: 500; +} + + +.info { + .content div:not(:last-child) { + margin-bottom: 16px; + } + + padding: 16px; + margin-bottom: 16px; + border: solid 2px; + border-radius: 4px; + display: flex; + + p { + margin: 0; + } + + mat-icon { + margin-right: 16px; + } + + +} + + // MATERIAL OVERWRITES ::ng-deep { + .mat-horizontal-stepper-wrapper { + .mat-horizontal-stepper-header-container { + display: none; + } + } + + mat-vertical-stepper.orcid-stepper-wizard mat-card p, mat-horizontal-stepper.orcid-stepper-wizard mat-card p { @@ -110,7 +156,7 @@ mat-label.orc-font-small-print { flex-direction: column; justify-content: space-between; width: 100%; - margin-top: 28px; + margin-top: 0px; gap: 12px; } } diff --git a/src/app/register2/components/step-a/step-a.component.html b/src/app/register2/components/step-a/step-a.component.html index 4c5ee5d412..e8fb42d7de 100644 --- a/src/app/register2/components/step-a/step-a.component.html +++ b/src/app/register2/components/step-a/step-a.component.html @@ -12,7 +12,7 @@ *ngIf="!reactivation?.isReactivation" i18n="@@register.create" > - Create your ORCID iD +

Create your ORCID iD

- This is step 1 of 3Step 1 of 4 - Names and emails @@ -48,6 +48,7 @@
@@ -57,6 +58,7 @@ matStepperNext i18n="@@shared.next" color="primary" + (click)="nextButton()" > NEXT STEP diff --git a/src/app/register2/components/step-a/step-a.component.ts b/src/app/register2/components/step-a/step-a.component.ts index fada13b9f0..3258d209d0 100644 --- a/src/app/register2/components/step-a/step-a.component.ts +++ b/src/app/register2/components/step-a/step-a.component.ts @@ -16,6 +16,7 @@ import { BaseStepDirective } from '../BaseStep' }) export class StepAComponent extends BaseStepDirective { @Input() reactivation: ReactivationLocal + nextButtonWasClicked = false constructor(private _platform: PlatformInfoService, private _router: Router) { super() @@ -49,6 +50,10 @@ export class StepAComponent extends BaseStepDirective { }) } + nextButton () { + this.nextButtonWasClicked = true + } + signIn() { this._platform .get() diff --git a/src/app/register2/components/step-b/step-b.component.html b/src/app/register2/components/step-b/step-b.component.html index 526d75477e..117a2a269c 100644 --- a/src/app/register2/components/step-b/step-b.component.html +++ b/src/app/register2/components/step-b/step-b.component.html @@ -12,8 +12,8 @@ *ngIf="!reactivation?.isReactivation" i18n="@@register.create" > - Create your ORCID iD - +

Create your ORCID iD

+ - This is step 2 of 3Step 2 of 4 - Password diff --git a/src/app/register2/components/step-c-t/step-c.component.html b/src/app/register2/components/step-c-t/step-c.component.html index 0c848e2b00..1119db1c70 100644 --- a/src/app/register2/components/step-c-t/step-c.component.html +++ b/src/app/register2/components/step-c-t/step-c.component.html @@ -18,8 +18,8 @@ *ngIf="!reactivation?.isReactivation" i18n="@@register.create" > - Create your ORCID iD - +

Create your ORCID iD

+ - This is step 3 of 4Step 3 of 4 - Visibility diff --git a/src/app/register2/components/step-d/step-d.component.html b/src/app/register2/components/step-d/step-d.component.html index deaf74c211..eb8527f5f0 100644 --- a/src/app/register2/components/step-d/step-d.component.html +++ b/src/app/register2/components/step-d/step-d.component.html @@ -18,8 +18,8 @@ *ngIf="!reactivation?.isReactivation" i18n="@@register.create" > - Create your ORCID iD - +

Create your ORCID iD

+ - This is step 4 of 4Step 4 of 4 - Terms and conditions diff --git a/src/app/register2/pages/register/register2.component.html b/src/app/register2/pages/register/register2.component.html index 9961699a70..6bde1f73c1 100644 --- a/src/app/register2/pages/register/register2.component.html +++ b/src/app/register2/pages/register/register2.component.html @@ -1,12 +1,10 @@
-
- +
diff --git a/src/app/types/register.email-category.ts b/src/app/types/register.email-category.ts new file mode 100644 index 0000000000..fd2180fc9a --- /dev/null +++ b/src/app/types/register.email-category.ts @@ -0,0 +1,5 @@ +export type EmailCategory = 'PROFESSIONAL' | 'PERSONAL' | 'UNKNOWN' + +export interface EmailCategoryEndpoint { + category: EmailCategory +} diff --git a/src/app/types/register.endpoint.ts b/src/app/types/register.endpoint.ts index edfd755d85..c1f863e0fe 100644 --- a/src/app/types/register.endpoint.ts +++ b/src/app/types/register.endpoint.ts @@ -43,3 +43,4 @@ export interface RegisterConfirmResponse { errors?: any[] url: string } + diff --git a/src/assets/scss/material.light-theme.scss b/src/assets/scss/material.light-theme.scss index 2516222ff2..aa1a7631dd 100644 --- a/src/assets/scss/material.light-theme.scss +++ b/src/assets/scss/material.light-theme.scss @@ -27,6 +27,7 @@ brand-primary-lightest: $brand-primary-lightest, ui-background-light: $ui-background-light, ui-background-lightest: $ui-background-lightest, + state-notice-lightest: $state-notice-lightest, ui-background-darkest: $ui-background-darkest, ui-background: $ui-background, // COLORS OVERWRITES diff --git a/src/assets/scss/material.palettes.scss b/src/assets/scss/material.palettes.scss index c9b9317312..992098c47f 100644 --- a/src/assets/scss/material.palettes.scss +++ b/src/assets/scss/material.palettes.scss @@ -139,6 +139,8 @@ $state-notice-light: #ffdf72; $state-notice-dark: #ff9c00; +$state-notice-lightest: #FFFBEE; + $state-notice-darkest: #ff6400; $state-warning-dark: #d32f2f; diff --git a/src/assets/vectors/personal-email-icon.svg b/src/assets/vectors/personal-email-icon.svg new file mode 100644 index 0000000000..4143b6a0bc --- /dev/null +++ b/src/assets/vectors/personal-email-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/vectors/professional-email-icon.svg b/src/assets/vectors/professional-email-icon.svg new file mode 100644 index 0000000000..9d87208847 --- /dev/null +++ b/src/assets/vectors/professional-email-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/index.html b/src/index.html index abffff8d94..77dcd82ef9 100644 --- a/src/index.html +++ b/src/index.html @@ -19,10 +19,7 @@ href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> - +