[CST-15074][#3355] PR review

This commit is contained in:
Vincenzo Mecca
2025-03-12 20:15:57 +01:00
parent f2c5912dfa
commit d6776a8a1e
9 changed files with 155 additions and 117 deletions

View File

@@ -11,6 +11,7 @@ import {
} from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { CookieAttributes } from 'js-cookie';
import uniqBy from 'lodash/uniqBy';
import {
Observable,
of as observableOf,
@@ -38,6 +39,7 @@ import {
isNotNull,
isNotUndefined,
} from '../../shared/empty.util';
import { rendersAuthMethodType } from '../../shared/log-in/methods/log-in.methods-decorator';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { followLink } from '../../shared/utils/follow-link-config.model';
@@ -74,6 +76,7 @@ import {
} from './auth.actions';
import { AuthRequestService } from './auth-request.service';
import { AuthMethod } from './models/auth.method';
import { AuthMethodType } from './models/auth.method-type';
import { AuthStatus } from './models/auth-status.model';
import {
AuthTokenInfo,
@@ -81,6 +84,7 @@ import {
} from './models/auth-token-info.model';
import {
getAuthenticatedUserId,
getAuthenticationMethods,
getAuthenticationToken,
getExternalAuthCookieStatus,
getRedirectUrl,
@@ -690,4 +694,18 @@ export class AuthService {
}
}
public getAuthMethods(excludedAuthMethod?: AuthMethodType): Observable<AuthMethod[]> {
return this.store.pipe(
select(getAuthenticationMethods),
map((methods: AuthMethod[]) => methods
// ignore the given auth method if it should be excluded
.filter((authMethod: AuthMethod) => excludedAuthMethod == null || authMethod.authMethodType !== excludedAuthMethod)
.filter((authMethod: AuthMethod) => rendersAuthMethodType(authMethod.authMethodType) !== undefined)
.sort((method1: AuthMethod, method2: AuthMethod) => method1.position - method2.position),
),
// ignore the ip authentication method when it's returned by the backend
map((authMethods: AuthMethod[]) => uniqBy(authMethods.filter(a => a.authMethodType !== AuthMethodType.Ip), 'authMethodType')),
);
}
}

View File

@@ -1,29 +1,37 @@
<h4 class="mb-3">
<h4>
{{ "external-login.confirm-email.header" | translate }}
</h4>
<form [formGroup]="emailForm" (ngSubmit)="submitForm()">
<div class="form-group">
<input
type="email"
id="email"
formControlName="email"
placeholder="profile.email@example.com"
class="form-control form-control-lg position-relative"
[attr.aria-label]="'external-login.confirmation.email-label' | translate"
/>
@if (emailForm.get('email').hasError('required') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-required" | translate }}
<div class="form-row">
<div class="col-12 my-2">
<input
type="email"
id="email"
formControlName="email"
placeholder="profile.email@example.com"
class="form-control form-control-lg position-relative"
[attr.aria-label]="'external-login.confirmation.email-label' | translate"
/>
@if (emailForm.get('email').hasError('required') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-required" | translate }}
</div>
}
@if (emailForm.get('email').hasError('email') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-invalid" | translate }}
</div>
}
</div>
}
@if (emailForm.get('email').hasError('email') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-invalid" | translate }}
</div>
<div class="form-row">
<div class="col-12">
<button type="submit" class="btn btn-lg btn-primary w-100">
{{ "external-login.confirm.button.label" | translate }}
</button>
</div>
}
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary btn-block">
{{ "external-login.confirm.button.label" | translate }}
</button>
</form>

View File

@@ -1,30 +1,37 @@
<h4 class="mb-3">
<h4>
{{ "external-login.provide-email.header" | translate }}
</h4>
<form [formGroup]="emailForm" (ngSubmit)="submitForm()">
<div class="form-group">
<input
type="email"
id="email"
formControlName="email"
class="form-control form-control-lg position-relative"
[attr.aria-label]="'external-login.confirmation.email' | translate"
/>
<div class="form-row">
<div class="col-12 my-2">
<input
type="email"
id="email"
formControlName="email"
class="form-control form-control-lg position-relative"
[attr.aria-label]="'external-login.confirmation.email' | translate"
/>
@if (emailForm.get('email').hasError('required') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-required" | translate }}
@if (emailForm.get('email').hasError('required') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-required" | translate }}
</div>
}
@if (emailForm.get('email').hasError('email') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-invalid" | translate }}
</div>
}
</div>
}
@if (emailForm.get('email').hasError('email') && emailForm.get('email').touched) {
<div class="text-danger">
{{ "external-login.confirmation.email-invalid" | translate }}
</div>
<div class="form-row">
<div class="col-12">
<button type="submit" class="btn btn-lg btn-primary w-100">
{{ "external-login.provide-email.button.label" | translate }}
</button>
</div>
}
</div>
</div>
<button type="submit" class="btn btn-lg btn-primary btn-block">
{{ "external-login.provide-email.button.label" | translate }}
</button>
</form>

View File

@@ -1,34 +1,38 @@
<div class="row">
<h4>{{ 'external-login.confirmation.header' | translate}}</h4>
<h4>{{ 'external-login.confirmation.header' | translate }}</h4>
</div>
<div class="row justify-content-center">
<ng-container *ngComponentOutlet="getExternalLoginConfirmationType(); injector: objectInjector;">
</ng-container>
</div>
<ds-alert class="row mt-2" [type]="AlertTypeEnum.Info" [attr.data-test]="'info-text'">
<ds-alert class="container mt-2" [type]="AlertTypeEnum.Info" [attr.data-test]="'info-text'">
{{ informationText }}
</ds-alert>
<div class="row justify-content-center">
<div class="col-4 d-flex justify-content-end align-items-center">
@if (registrationData.email) {
<ds-confirm-email [registrationData]="registrationData" [token]="token"></ds-confirm-email>
} @else {
<div class="row d-flex justify-content-center">
<div class="col-6 d-flex">
<div class="col d-flex justify-content-center align-items-center">
@if (registrationData.email) {
<ds-confirm-email [registrationData]="registrationData" [token]="token"></ds-confirm-email>
} @else {
<ds-provide-email [registrationId]="registrationData.id" [token]="token"></ds-provide-email>
}
</div>
<div class="col-1 align-items-center d-flex justify-content-center">
<h4 class="mt-2">{{ 'external-login.component.or' | translate }}</h4>
</div>
<div class="col-4 align-items-center d-flex justify-content-start">
<button class="btn block btn-lg btn-primary" (click)="openLoginModal(loginModal)">
{{'external-login.connect-to-existing-account.label' | translate}}
</button>
</div>
@if (hasAuthMethodTypes | async) {
<div class="col-1 d-flex justify-content-center align-items-center">
<h4 class="mt-2">{{ 'external-login.component.or' | translate }}</h4>
</div>
<div class="col d-flex justify-content-center align-items-center">
<button class="btn block btn-lg btn-primary" (click)="openLoginModal(loginModal)">
{{ 'external-login.connect-to-existing-account.label' | translate }}
</button>
</div>
}
</div>
</div>
<ng-template #loginModal let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title text-info"> {{'external-login.connect-to-existing-account.label' | translate}}</h4>
<h4 class="modal-title text-info"> {{ 'external-login.connect-to-existing-account.label' | translate }}</h4>
</div>
<div class="modal-body">
<div class="row justify-content-center">
@@ -42,7 +46,7 @@
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="c('Close click');clearRedirectUrl()">
<i class="fa fa-times" aria-hidden="true"></i>
{{'external-login.modal.label.close' | translate}}
{{ 'external-login.modal.label.close' | translate }}
</button>
</div>
</ng-template>

View File

@@ -1,4 +1,7 @@
import { NgComponentOutlet } from '@angular/common';
import {
AsyncPipe,
NgComponentOutlet,
} from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@@ -15,6 +18,8 @@ import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service';
import { AuthMethodType } from '../../core/auth/models/auth.method-type';
@@ -46,13 +51,14 @@ import { ProvideEmailComponent } from '../email-confirmation/provide-email/provi
ConfirmEmailComponent,
ThemedLogInComponent,
NgComponentOutlet,
AsyncPipe,
],
standalone: true,
})
/**
* This component is responsible to handle the external-login depending on the RegistrationData details provided
*/
export class ExternalLogInComponent implements OnInit, OnDestroy {
export class ExternalLogInComponent implements OnInit, OnDestroy {
/**
* The AlertType enumeration for access in the component's template
* @type {AlertType}
@@ -93,13 +99,18 @@ export class ExternalLogInComponent implements OnInit, OnDestroy {
* Authentication method related to registration type
*/
relatedAuthMethod: AuthMethodType;
/**
* The observable to check if any auth method type is configured
*/
hasAuthMethodTypes: Observable<boolean>;
constructor(
private injector: Injector,
private translate: TranslateService,
private modalService: NgbModal,
private authService: AuthService,
) { }
) {
}
/**
* Provide the registration data object to the objectInjector.
@@ -121,6 +132,38 @@ export class ExternalLogInComponent implements OnInit, OnDestroy {
this.informationText = hasValue(this.registrationData?.email)
? this.generateInformationTextWhenEmail(this.registrationType)
: this.generateInformationTextWhenNOEmail(this.registrationType);
this.hasAuthMethodTypes = this.authService.getAuthMethods(this.relatedAuthMethod).pipe(map(methods => methods.length > 0));
}
/**
* Get the registration type to be rendered
*/
getExternalLoginConfirmationType(): ExternalLoginTypeComponent {
return getExternalLoginConfirmationType(this.registrationType);
}
/**
* Opens the login modal and sets the redirect URL to '/review-account'.
* On modal dismissed/closed, the redirect URL is cleared.
* @param content - The content to be displayed in the modal.
*/
openLoginModal(content: any) {
this.modalRef = this.modalService.open(content);
this.authService.setRedirectUrl(`/review-account/${this.token}`);
this.modalRef.dismissed.subscribe(() => {
this.clearRedirectUrl();
});
}
/**
* Clears the redirect URL stored in the authentication service.
*/
clearRedirectUrl() {
this.authService.clearRedirectUrl();
}
ngOnDestroy(): void {
this.modalRef?.close();
}
/**
@@ -149,38 +192,4 @@ export class ExternalLogInComponent implements OnInit, OnDestroy {
);
}
}
/**
* Get the registration type to be rendered
*/
getExternalLoginConfirmationType(): ExternalLoginTypeComponent {
return getExternalLoginConfirmationType(this.registrationType);
}
/**
* Opens the login modal and sets the redirect URL to '/review-account'.
* On modal dismissed/closed, the redirect URL is cleared.
* @param content - The content to be displayed in the modal.
*/
openLoginModal(content: any) {
setTimeout(() => {
this.authService.setRedirectUrl(`/review-account/${this.token}`);
}, 100);
this.modalRef = this.modalService.open(content);
this.modalRef.dismissed.subscribe(() => {
this.clearRedirectUrl();
});
}
/**
* Clears the redirect URL stored in the authentication service.
*/
clearRedirectUrl() {
this.authService.clearRedirectUrl();
}
ngOnDestroy(): void {
this.modalRef?.close();
}
}

View File

@@ -23,7 +23,7 @@
<td>{{ registrationData.netId }}</td>
<td>
<span>
{{ 'external-login-validation.review-account-info.table.row.not-applicable' }}
{{ 'external-login-validation.review-account-info.table.row.not-applicable' | translate }}
</span>
</td>
<td></td>
@@ -55,7 +55,7 @@
</tbody>
</table> <div class="d-flex justify-content-end">
<button class="btn btn-primary" (click)="onSave()">
{{'confirmation-modal.review-account-info.confirm' | translate}}
{{'confirmation-modal.review-account-info.save' | translate}}
</button>
</div>
</div>

View File

@@ -9,16 +9,13 @@ import {
select,
Store,
} from '@ngrx/store';
import uniqBy from 'lodash/uniqBy';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service';
import { AuthMethod } from '../../core/auth/models/auth.method';
import { AuthMethodType } from '../../core/auth/models/auth.method-type';
import {
getAuthenticationError,
getAuthenticationMethods,
isAuthenticated,
isAuthenticationLoading,
} from '../../core/auth/selectors';
@@ -26,7 +23,6 @@ import { CoreState } from '../../core/core-state.model';
import { hasValue } from '../empty.util';
import { ThemedLoadingComponent } from '../loading/themed-loading.component';
import { LogInContainerComponent } from './container/log-in-container.component';
import { rendersAuthMethodType } from './methods/log-in.methods-decorator';
@Component({
selector: 'ds-base-log-in',
@@ -77,17 +73,7 @@ export class LogInComponent implements OnInit {
}
ngOnInit(): void {
this.authMethods = this.store.pipe(
select(getAuthenticationMethods),
map((methods: AuthMethod[]) => methods
// ignore the given auth method if it should be excluded
.filter((authMethod: AuthMethod) => authMethod.authMethodType !== this.excludedAuthMethod)
.filter((authMethod: AuthMethod) => rendersAuthMethodType(authMethod.authMethodType) !== undefined)
.sort((method1: AuthMethod, method2: AuthMethod) => method1.position - method2.position),
),
// ignore the ip authentication method when it's returned by the backend
map((authMethods: AuthMethod[]) => uniqBy(authMethods.filter(a => a.authMethodType !== AuthMethodType.Ip), 'authMethodType')),
);
this.authMethods = this.authService.getAuthMethods(this.excludedAuthMethod);
// set loading
this.loading = this.store.pipe(select(isAuthenticationLoading));

View File

@@ -1826,11 +1826,13 @@
"confirmation-modal.review-account-info.header": "Save the changes",
"confirmation-modal.review-account-info.info": "Continue to update your profile",
"confirmation-modal.review-account-info.info": "Are you sure you want to save the changes to your profile",
"confirmation-modal.review-account-info.cancel": "Cancel",
"confirmation-modal.review-account-info.confirm": "Save",
"confirmation-modal.review-account-info.confirm": "Confirm",
"confirmation-modal.review-account-info.save": "Save",
"error.bitstream": "Error fetching bitstream",

View File

@@ -2309,17 +2309,21 @@
// TODO New key - Add a translation
"confirmation-modal.review-account-info.header": "Save the changes",
// "confirmation-modal.review-account-info.info": "Continue to update your profile",
// "confirmation-modal.review-account-info.info": "Are you sure you want to save the changes to your profile",
// TODO New key - Add a translation
"confirmation-modal.review-account-info.info": "Continue to update your profile",
"confirmation-modal.review-account-info.info": "Are you sure you want to save the changes to your profile?",
// "confirmation-modal.review-account-info.cancel": "Cancel",
// TODO New key - Add a translation
"confirmation-modal.review-account-info.cancel": "Cancel",
// "confirmation-modal.review-account-info.confirm": "Save",
// "confirmation-modal.review-account-info.confirm": "Confirm",
// TODO New key - Add a translation
"confirmation-modal.review-account-info.confirm": "Save",
"confirmation-modal.review-account-info.confirm": "Confirm",
// "confirmation-modal.review-account-info.save": "Save",
// TODO New key - Add a translation
"confirmation-modal.review-account-info.save": "Save",
// "error.bitstream": "Error fetching bitstream",
"error.bitstream": "Errore durante il recupero del bitstream",