@dsivd/prestations-ng 18.2.4 → 18.3.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,13 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## [18.3.0] - should be aligned with prestations-be 18.3.x
10
+
11
+ ### Added
12
+
13
+ - [gesdem-action-recovery-login.component.ts](projects/prestations-ng/src/gesdem-action-recovery/gesdem-action-recovery-login/gesdem-action-recovery-login.component.ts)
14
+ - Display a countdown to get a new OTP during action recovery (to handle backend rate limiter)
15
+
9
16
  ## [18.2.4]
10
17
 
11
18
  ### Fixed
@@ -2,8 +2,8 @@ import * as i0 from '@angular/core';
2
2
  import { Optional, Inject, Injectable, EventEmitter, Input, HostBinding, Output, forwardRef, ViewChildren, ViewChild, Directive, Pipe, Component, ContentChildren, HostListener, NgModule, inject, TemplateRef, ContentChild } from '@angular/core';
3
3
  import * as i1$1 from '@angular/router';
4
4
  import { ActivatedRoute, NavigationStart, RouterModule, NavigationEnd, RouterLink } from '@angular/router';
5
- import { of, Subject, BehaviorSubject, combineLatest, throwError, switchMap as switchMap$1, forkJoin, tap as tap$1, concat, toArray, EMPTY, startWith, filter as filter$1, merge, withLatestFrom, debounceTime as debounceTime$1, map as map$1 } from 'rxjs';
6
- import { map, shareReplay, filter, tap, debounceTime, catchError, switchMap, first, throttleTime, mergeMap, share, finalize, distinctUntilChanged } from 'rxjs/operators';
5
+ import { of, Subject, BehaviorSubject, combineLatest, throwError, switchMap as switchMap$1, forkJoin, tap as tap$1, concat, toArray, EMPTY, startWith, filter as filter$1, merge, interval, withLatestFrom, debounceTime as debounceTime$1, map as map$1 } from 'rxjs';
6
+ import { map, shareReplay, filter, tap, debounceTime, catchError, switchMap, first, throttleTime, mergeMap, share, finalize, take, distinctUntilChanged } from 'rxjs/operators';
7
7
  import * as i1 from '@angular/common/http';
8
8
  import { HttpStatusCode, HttpResponseBase, HttpErrorResponse, HttpParams, HttpEventType, HttpResponse, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi, HttpClient } from '@angular/common/http';
9
9
  import * as i3 from '@angular/forms';
@@ -1253,6 +1253,10 @@ const DEFAULT_DICTIONARY = {
1253
1253
  'gesdem-confirmation.resume-my-demand.label': 'Vous pouvez reprendre votre demande en cliquant sur le lien ci-dessous',
1254
1254
  'gesdem-confirmation.resume-my-demand.button': 'Reprendre ma demande',
1255
1255
  'gesdem.download-pdf.button.title': 'Télécharger le PDF',
1256
+ 'gesdem-action-recovery-login.request-now-code.label': 'Pour obtenir un nouveau code, vous devez recliquer sur le ' +
1257
+ '<a href="{redirectTarget}">lien</a> de reprise de la demande.\n',
1258
+ 'gesdem-action-recovery-login.rate-limited.label': "Merci d'attendre {rateLimitCounter} seconde{plural} " +
1259
+ 'pour demander un nouveau code.',
1256
1260
  'gesdem-action-recovery-login.save.label': 'Les données de votre demande enregistrée le {date} ont été chargées.',
1257
1261
  'gesdem-action-recovery-login.invalid-otp.label': "Le mot de passe saisi n'est pas valide",
1258
1262
  'gesdem-action-recovery-login.password-generation-failed.label': 'Impossible de générer un mot de passe, veuillez réessayer plus tard.',
@@ -1409,7 +1413,7 @@ const DEFAULT_DICTIONARY = {
1409
1413
  'gesdem-error.title.error': 'Erreur rencontrée',
1410
1414
  'gesdem-error.title.recovery': 'Reprise de la demande',
1411
1415
  'gesdem-error.text.wrong-etape-requested': 'La demande {reference} ne peut plus être modifiée via ce formulaire',
1412
- 'gesdem-error.text.demande-not-found': 'La demande {reference} a été supprimée',
1416
+ 'gesdem-error.text.demande-not-found': 'Les conditions préalables pour la reprise de cette demande ne sont pas remplies',
1413
1417
  'gesdem-error.text.has-reference': 'Vous pouvez essayer de reprendre votre demande en cliquant sur le lien ci-dessous',
1414
1418
  'gesdem-error.link.has-reference': 'Reprendre ma demande',
1415
1419
  'gesdem-error.text.has-no-reference': 'Vous pouvez recommencer une demande en cliquant sur le lien ci-dessous',
@@ -1966,7 +1970,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
1966
1970
  const SESSION_EXPIRED = 'SESSION_EXPIRED';
1967
1971
  const DEMANDE_SHOULD_EXIST = 'DEMANDE_SHOULD_EXIST';
1968
1972
  const WRONG_ETAPE_REQUESTED = 'WRONG_ETAPE_REQUESTED';
1969
- const DEMANDE_NOT_FOUND = 'DEMANDE_NOT_FOUND';
1973
+ const OBJECT_NOT_FOUND = 'OBJECT_NOT_FOUND';
1970
1974
  class GesdemErrorHandlerService {
1971
1975
  constructor(growlService, router, iamExpiredInterceptorService) {
1972
1976
  this.growlService = growlService;
@@ -5667,12 +5671,12 @@ class GesdemActionRecoveryLoginComponent {
5667
5671
  this.router = router;
5668
5672
  this.route = route;
5669
5673
  this.eventsLoggerService = eventsLoggerService;
5670
- this.missingRecoveryInfos = false;
5671
- this.alreadyTransferred = false;
5674
+ this.demandeNotFound = false;
5675
+ this.rateLimited = false;
5676
+ this.rateLimitCounter = 0;
5672
5677
  }
5673
5678
  ngOnInit() {
5674
5679
  this.otpRecipient = null;
5675
- this.missingRecoveryInfos = false;
5676
5680
  this.applicationInfoService.currentEtapeInfo
5677
5681
  .pipe(tap(currentEtape => {
5678
5682
  if (currentEtape.reprisePossible) {
@@ -5698,6 +5702,9 @@ class GesdemActionRecoveryLoginComponent {
5698
5702
  // eslint-disable-next-line rxjs-angular/prefer-async-pipe
5699
5703
  .subscribe();
5700
5704
  }
5705
+ ngOnDestroy() {
5706
+ this.rateLimitCounterSub?.unsubscribe();
5707
+ }
5701
5708
  validateOtp() {
5702
5709
  this.eventsLoggerService.addEvent(SdkEventType.RECOVERY_LOGIN_STARTED);
5703
5710
  if (!this.route.snapshot.queryParams.reCaptchaByPassUUID &&
@@ -5763,35 +5770,56 @@ class GesdemActionRecoveryLoginComponent {
5763
5770
  }
5764
5771
  });
5765
5772
  }
5773
+ getPluralMarker(count) {
5774
+ return this.dictionaryService.getPluralMarkerSync(count);
5775
+ }
5766
5776
  startRecovery() {
5777
+ this.rateLimited = false;
5778
+ this.demandeNotFound = false;
5767
5779
  this.recoveryService
5768
5780
  .startRecovery(this.reference)
5769
5781
  .pipe(catchError((response) => {
5770
5782
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5771
5783
  const { error, status } = response;
5772
- this.missingRecoveryInfos =
5784
+ this.rateLimited = status === 429;
5785
+ if (this.rateLimited) {
5786
+ this.startRateLimitCounter();
5787
+ return EMPTY;
5788
+ }
5789
+ this.demandeNotFound =
5773
5790
  status === 404 &&
5774
5791
  error &&
5775
- JSON.parse(error).code === 'ACTION_RECOVERY_NOT_FOUND';
5776
- this.alreadyTransferred =
5777
- status === 409 &&
5778
- error &&
5779
- JSON.parse(error).code === 'ACTION_ALREADY_TRANSFERRED';
5780
- if (!this.missingRecoveryInfos &&
5781
- !this.alreadyTransferred) {
5782
- this.growlService.addWithType(GrowlType.DANGER, this.dictionaryService.getKeySync('gesdem-action-recovery-login.password-generation-failed.label'));
5792
+ JSON.parse(error).code === OBJECT_NOT_FOUND;
5793
+ if (this.demandeNotFound) {
5794
+ return EMPTY;
5783
5795
  }
5796
+ this.growlService.addWithType(GrowlType.DANGER, this.dictionaryService.getKeySync('gesdem-action-recovery-login.password-generation-failed.label'));
5784
5797
  return throwError(() => true);
5785
5798
  }))
5786
5799
  // eslint-disable-next-line rxjs-angular/prefer-async-pipe
5787
- .subscribe(otpRecipient => (this.otpRecipient = otpRecipient));
5800
+ .subscribe(otpRecipient => {
5801
+ this.otpRecipient = otpRecipient;
5802
+ this.startRateLimitCounter();
5803
+ });
5804
+ }
5805
+ startRateLimitCounter() {
5806
+ this.rateLimitCounter = 30;
5807
+ const rateLimitCounterSub = interval(1000)
5808
+ .pipe(take(30))
5809
+ // eslint-disable-next-line rxjs-angular/prefer-async-pipe
5810
+ .subscribe(() => {
5811
+ this.rateLimitCounter--;
5812
+ if (this.rateLimitCounter === 0) {
5813
+ rateLimitCounterSub?.unsubscribe();
5814
+ }
5815
+ });
5788
5816
  }
5789
5817
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GesdemActionRecoveryLoginComponent, deps: [{ token: GesdemActionRecoveryService }, { token: GrowlBrokerService }, { token: FoehnConfirmModalService }, { token: ValidationHandlerService }, { token: ApplicationInfoService }, { token: GesdemHandlerService }, { token: SdkDictionaryService }, { token: i1$1.Router }, { token: i1$1.ActivatedRoute }, { token: SdkEventsLoggerService }], target: i0.ɵɵFactoryTarget.Component }); }
5790
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: GesdemActionRecoveryLoginComponent, isStandalone: false, selector: "gesdem-action-recovery-login", inputs: { reference: "reference", redirectTarget: "redirectTarget" }, viewQueries: [{ propertyName: "loginForm", first: true, predicate: FoehnFormComponent, descendants: true }, { propertyName: "recaptchaComponent", first: true, predicate: SdkRecaptchaComponent, descendants: true }], ngImport: i0, template: "<div class=\"container mt-5\">\n <div class=\"alert alert-danger\" *ngIf=\"missingRecoveryInfos\">\n Vous n'avez pas indiqu\u00E9 d'informations de contact pour la reprise de\n cette demande, merci de contacter le support.\n </div>\n <div class=\"alert alert-danger\" *ngIf=\"alreadyTransferred\">\n La demande que vous essayez de reprendre a d\u00E9j\u00E0 \u00E9t\u00E9 transmise, elle\n n'est plus modifiable.\n </div>\n\n <foehn-form [shouldDisplayAlertSummary]=\"false\" *ngIf=\"otpRecipient\">\n <div class=\"row\">\n <div class=\"col-md-8\">\n <foehn-input-text\n [label]=\"\n 'Veuillez saisir le code envoy\u00E9 par SMS au num\u00E9ro ' +\n otpRecipient\n \"\n [(model)]=\"otp\"\n name=\"otp\"\n [required]=\"true\"\n ></foehn-input-text>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n Pour obtenir un nouveau code, vous devez recliquer sur le\n <a [href]=\"redirectTarget\">lien</a>\n de reprise de la demande\n </div>\n </div>\n\n <br />\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n <captcha></captcha>\n\n <button\n id=\"nextButton\"\n type=\"submit\"\n class=\"btn btn-dark float-end\"\n (click)=\"validateOtp()\"\n >\n Suivant\n </button>\n </div>\n </div>\n </foehn-form>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FoehnInputTextComponent, selector: "foehn-input-text", inputs: ["numberOnly"] }, { kind: "component", type: FoehnFormComponent, selector: "foehn-form", inputs: ["shouldDisplayAlertSummary"] }, { kind: "component", type: SdkRecaptchaComponent, selector: "captcha" }] }); }
5818
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.17", type: GesdemActionRecoveryLoginComponent, isStandalone: false, selector: "gesdem-action-recovery-login", inputs: { reference: "reference", redirectTarget: "redirectTarget" }, viewQueries: [{ propertyName: "loginForm", first: true, predicate: FoehnFormComponent, descendants: true }, { propertyName: "recaptchaComponent", first: true, predicate: SdkRecaptchaComponent, descendants: true }], ngImport: i0, template: "<div class=\"container mt-5\">\n <div\n class=\"alert alert-danger\"\n *ngIf=\"demandeNotFound\"\n [innerHTML]=\"'gesdem-error.text.demande-not-found' | fromDictionary\"\n ></div>\n\n <div class=\"alert alert-danger\" *ngIf=\"rateLimited\">\n @if (rateLimitCounter > 0) {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.rate-limited.label'\n | fromDictionary\n : {\n rateLimitCounter:\n rateLimitCounter?.toString(),\n plural: getPluralMarker(rateLimitCounter)\n }\n \"\n ></span>\n } @else {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.request-now-code.label'\n | fromDictionary: { redirectTarget: redirectTarget }\n \"\n ></span>\n }\n </div>\n\n <foehn-form [shouldDisplayAlertSummary]=\"false\" *ngIf=\"otpRecipient\">\n <div class=\"row\">\n <div class=\"col-md-8\">\n <foehn-input-text\n [label]=\"\n 'Veuillez saisir le code envoy\u00E9 par SMS au num\u00E9ro ' +\n otpRecipient\n \"\n [(model)]=\"otp\"\n name=\"otp\"\n [required]=\"true\"\n ></foehn-input-text>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n @if (rateLimitCounter > 0) {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.rate-limited.label'\n | fromDictionary\n : {\n rateLimitCounter:\n rateLimitCounter?.toString(),\n plural: getPluralMarker(\n rateLimitCounter\n )\n }\n \"\n ></span>\n } @else {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.request-now-code.label'\n | fromDictionary\n : { redirectTarget: redirectTarget }\n \"\n ></span>\n }\n </div>\n </div>\n\n <br />\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n <captcha></captcha>\n\n <button\n id=\"nextButton\"\n type=\"submit\"\n class=\"btn btn-dark float-end\"\n (click)=\"validateOtp()\"\n >\n Suivant\n </button>\n </div>\n </div>\n </foehn-form>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FoehnInputTextComponent, selector: "foehn-input-text", inputs: ["numberOnly"] }, { kind: "component", type: FoehnFormComponent, selector: "foehn-form", inputs: ["shouldDisplayAlertSummary"] }, { kind: "component", type: SdkRecaptchaComponent, selector: "captcha" }, { kind: "pipe", type: SdkDictionaryPipe, name: "fromDictionary" }] }); }
5791
5819
  }
5792
5820
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: GesdemActionRecoveryLoginComponent, decorators: [{
5793
5821
  type: Component,
5794
- args: [{ selector: 'gesdem-action-recovery-login', standalone: false, template: "<div class=\"container mt-5\">\n <div class=\"alert alert-danger\" *ngIf=\"missingRecoveryInfos\">\n Vous n'avez pas indiqu\u00E9 d'informations de contact pour la reprise de\n cette demande, merci de contacter le support.\n </div>\n <div class=\"alert alert-danger\" *ngIf=\"alreadyTransferred\">\n La demande que vous essayez de reprendre a d\u00E9j\u00E0 \u00E9t\u00E9 transmise, elle\n n'est plus modifiable.\n </div>\n\n <foehn-form [shouldDisplayAlertSummary]=\"false\" *ngIf=\"otpRecipient\">\n <div class=\"row\">\n <div class=\"col-md-8\">\n <foehn-input-text\n [label]=\"\n 'Veuillez saisir le code envoy\u00E9 par SMS au num\u00E9ro ' +\n otpRecipient\n \"\n [(model)]=\"otp\"\n name=\"otp\"\n [required]=\"true\"\n ></foehn-input-text>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n Pour obtenir un nouveau code, vous devez recliquer sur le\n <a [href]=\"redirectTarget\">lien</a>\n de reprise de la demande\n </div>\n </div>\n\n <br />\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n <captcha></captcha>\n\n <button\n id=\"nextButton\"\n type=\"submit\"\n class=\"btn btn-dark float-end\"\n (click)=\"validateOtp()\"\n >\n Suivant\n </button>\n </div>\n </div>\n </foehn-form>\n</div>\n" }]
5822
+ args: [{ selector: 'gesdem-action-recovery-login', standalone: false, template: "<div class=\"container mt-5\">\n <div\n class=\"alert alert-danger\"\n *ngIf=\"demandeNotFound\"\n [innerHTML]=\"'gesdem-error.text.demande-not-found' | fromDictionary\"\n ></div>\n\n <div class=\"alert alert-danger\" *ngIf=\"rateLimited\">\n @if (rateLimitCounter > 0) {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.rate-limited.label'\n | fromDictionary\n : {\n rateLimitCounter:\n rateLimitCounter?.toString(),\n plural: getPluralMarker(rateLimitCounter)\n }\n \"\n ></span>\n } @else {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.request-now-code.label'\n | fromDictionary: { redirectTarget: redirectTarget }\n \"\n ></span>\n }\n </div>\n\n <foehn-form [shouldDisplayAlertSummary]=\"false\" *ngIf=\"otpRecipient\">\n <div class=\"row\">\n <div class=\"col-md-8\">\n <foehn-input-text\n [label]=\"\n 'Veuillez saisir le code envoy\u00E9 par SMS au num\u00E9ro ' +\n otpRecipient\n \"\n [(model)]=\"otp\"\n name=\"otp\"\n [required]=\"true\"\n ></foehn-input-text>\n </div>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n @if (rateLimitCounter > 0) {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.rate-limited.label'\n | fromDictionary\n : {\n rateLimitCounter:\n rateLimitCounter?.toString(),\n plural: getPluralMarker(\n rateLimitCounter\n )\n }\n \"\n ></span>\n } @else {\n <span\n [innerHTML]=\"\n 'gesdem-action-recovery-login.request-now-code.label'\n | fromDictionary\n : { redirectTarget: redirectTarget }\n \"\n ></span>\n }\n </div>\n </div>\n\n <br />\n\n <div class=\"row\">\n <div class=\"col-md-8\">\n <captcha></captcha>\n\n <button\n id=\"nextButton\"\n type=\"submit\"\n class=\"btn btn-dark float-end\"\n (click)=\"validateOtp()\"\n >\n Suivant\n </button>\n </div>\n </div>\n </foehn-form>\n</div>\n" }]
5795
5823
  }], ctorParameters: () => [{ type: GesdemActionRecoveryService }, { type: GrowlBrokerService }, { type: FoehnConfirmModalService }, { type: ValidationHandlerService }, { type: ApplicationInfoService }, { type: GesdemHandlerService }, { type: SdkDictionaryService }, { type: i1$1.Router }, { type: i1$1.ActivatedRoute }, { type: SdkEventsLoggerService }], propDecorators: { reference: [{
5796
5824
  type: Input
5797
5825
  }], redirectTarget: [{
@@ -5839,18 +5867,18 @@ class GesdemErrorComponent {
5839
5867
  }
5840
5868
  }
5841
5869
  ngOnInit() {
5870
+ const errorCode = this.activatedRoute.snapshot.queryParamMap.get('errorCode');
5842
5871
  if (this.reference) {
5843
5872
  const firstPage = this.getFirstPage();
5844
5873
  if (this.gesdemService.prefix) {
5845
5874
  this.link += `${this.gesdemService.prefix}/`;
5846
5875
  }
5847
5876
  this.link += `${this.reference}/${firstPage.path}`;
5848
- const errorCode = this.activatedRoute.snapshot.queryParamMap.get('errorCode');
5849
5877
  this.hasSessionExpired = errorCode === SESSION_EXPIRED;
5850
5878
  this.demandeShouldExist = errorCode === DEMANDE_SHOULD_EXIST;
5851
5879
  this.wrongEtapeRequested = errorCode === WRONG_ETAPE_REQUESTED;
5852
- this.demandeNotFound = errorCode === DEMANDE_NOT_FOUND;
5853
5880
  }
5881
+ this.demandeNotFound = errorCode === OBJECT_NOT_FOUND;
5854
5882
  // The error might have been caused by an issue with the prestation that has been identified
5855
5883
  // whilst the user is using the prestation. This ensures that if any alert is added by the
5856
5884
  // administrator, the user will at least see them if an error is shown.