From 57d0fa80b737e677c820bac5dfc70e42bc4fb343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Bouli=C3=A8re?= <108473857+tbouliere-datasolution@users.noreply.github.com> Date: Thu, 29 Jun 2023 09:34:02 +0200 Subject: [PATCH 01/46] fix: remove apiToken cookie if a request fails due to an invalid token (#1452) In the case of local development, you can change the target back-end by modifying the value of the variable icmBaseURL' variable in environment.developement.ts. By doing so, you will need a new api token. However, as your local DNS is still \"localhost\", there is no token renewal and the old token is kept. This small modification corrects this problem. --------- Co-authored-by: Silke --- src/app/core/utils/api-token/api-token.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/utils/api-token/api-token.service.ts b/src/app/core/utils/api-token/api-token.service.ts index e0ceba05ec..637de8fdb2 100644 --- a/src/app/core/utils/api-token/api-token.service.ts +++ b/src/app/core/utils/api-token/api-token.service.ts @@ -271,7 +271,7 @@ export class ApiTokenService { private isAuthTokenError(err: unknown) { return ( - err instanceof HttpErrorResponse && typeof err.error === 'string' && err.error.includes('AuthenticationToken') + err instanceof HttpErrorResponse && typeof err.error === 'string' && err.error?.toLowerCase().includes('token') ); } From 8d9a8035fd2311e5d2eb1199fad68f3b78d24445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silke=20Gr=C3=BCber?= Date: Fri, 30 Jun 2023 17:02:26 +0200 Subject: [PATCH 02/46] fix: minor address form issues (#1451) --- .../formly-address-form.component.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/shared/formly-address-forms/components/formly-address-form/formly-address-form.component.ts b/src/app/shared/formly-address-forms/components/formly-address-form/formly-address-form.component.ts index 6548da13b1..5f92be8c02 100644 --- a/src/app/shared/formly-address-forms/components/formly-address-form/formly-address-form.component.ts +++ b/src/app/shared/formly-address-forms/components/formly-address-form/formly-address-form.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleCha import { FormControl, FormGroup } from '@angular/forms'; import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core'; import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { filter, map, shareReplay, tap } from 'rxjs/operators'; import { AppFacade } from 'ish-core/facades/app.facade'; import { Address } from 'ish-core/models/address/address.model'; @@ -50,9 +50,12 @@ export class FormlyAddressFormComponent implements OnInit, OnChanges { constructor(private appFacade: AppFacade, private afcProvider: AddressFormConfigurationProvider) {} ngOnInit(): void { - this.countries$ = this.appFacade - .countries$() - ?.pipe(map(countries => countries?.map(country => ({ value: country.countryCode, label: country.name })))); + this.countries$ = this.appFacade.countries$()?.pipe( + filter(countries => !!countries?.length), + map(countries => countries?.map(country => ({ value: country.countryCode, label: country.name }))), + tap(() => this.fillForm(this.prefilledAddress)), + shareReplay(1) + ); this.initForm(); this.parentForm?.setControl('address', this.addressForm); } @@ -123,13 +126,17 @@ export class FormlyAddressFormComponent implements OnInit, OnChanges { ...configuration.getModel(), }; this.addressFields = [this.createCountrySelectField()].concat(configuration.getFieldConfiguration()); - this.fillForm(this.prefilledAddress); } private fillForm(prefilledAddress: Partial
= {}) { - if (!this.addressForm || Object.keys(prefilledAddress).length === 0) { + if (!this.addressForm) { return; } + if (Object.keys(prefilledAddress).length === 0) { + this.addressModel = { + countryCode: '', + }; + } this.addressModel.countryCode = prefilledAddress.countryCode; this.handleCountryChange(this.addressModel); From 38d8662370e1da1a9fa2dd65fea08b81f44ff22d Mon Sep 17 00:00:00 2001 From: Marcus Schmidt <42213508+marschmidt89@users.noreply.github.com> Date: Tue, 4 Jul 2023 09:22:53 +0200 Subject: [PATCH 03/46] docs: concept for CAPTCHA in the PWA (#1453) --------- Co-authored-by: Silke --- .vscode/intershop.txt | 1 + docs/README.md | 1 + docs/concepts/captcha-class-diagram.png | Bin 0 -> 152935 bytes docs/concepts/captcha.md | 110 ++++++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 docs/concepts/captcha-class-diagram.png create mode 100644 docs/concepts/captcha.md diff --git a/.vscode/intershop.txt b/.vscode/intershop.txt index b6d28f6d3a..bcdd6ede35 100644 --- a/.vscode/intershop.txt +++ b/.vscode/intershop.txt @@ -29,6 +29,7 @@ cxml cybersource datepicker decamelize +deth directdebit dockerignore dockerized diff --git a/docs/README.md b/docs/README.md index 99a125b5f3..206c9bdd0f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -47,6 +47,7 @@ kb_sync_latest_only - [Guide - Data Handling with Mappers](./guides/data-handling-with-mappers.md) - [Guide - Working with ESLint](./guides/eslint.md) - [Guide - Accessibility](./guides/accessibility.md) +- [Concept - Captcha](./concepts/captcha.md) ### Customization diff --git a/docs/concepts/captcha-class-diagram.png b/docs/concepts/captcha-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..0e6da15300bb2197a34273bc97454b003a6ee660 GIT binary patch literal 152935 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

Y+z0zv*wwZ_`0KBe_=-5 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 literal 0 HcmV?d00001 diff --git a/docs/concepts/captcha.md b/docs/concepts/captcha.md new file mode 100644 index 0000000000..2bb955ccb4 --- /dev/null +++ b/docs/concepts/captcha.md @@ -0,0 +1,110 @@ + + +# CAPTCHA in the PWA + +## Introduction + +The PWA supports reCAPTCHA V2 as well as reCAPTCHA V3. +Which CAPTCHA version is used depends on which CAPTCHA service is created and running in the ICM. +The pure CAPTCHA functionality is implemented as a PWA extension and can be found in the _extensions_ folder. +There are two basic CAPTCHA components (one for each CAPTCHA version) and a lazy CAPTCHA component that decides which one to use. + +The PWA uses the [Angular Formly framework](https://formly.dev/docs/guide/getting-started) to process web forms, see also [The Formly Guide](../guides/formly.md). +This framework allows you to automatically generate your forms using predefined form field types. +A special type `ish-captcha-field` has been implemented to support the CAPTCHA functionality. +You can define a field of this type in any _Formly_ form, so that this form is validated according to CAPTCHA. + +## High Level Overview + +The following class diagram shows the major classes of the CAPTCHA workflow that are relevant for most use cases: + +![Captcha Class Diagram](captcha-class-diagram.png) + +- _CAPTCHA Basic Components_ contains the recommended necessary UI components to integrate the Google reCAPTCHA service. +- The lazy component ensures that the component for the corresponding version is reloaded asynchronously. +- The CAPTCHA facade contains methods to retrieve the sitekey and the reCAPTCHA version used. +- The PWA uses the Formly framework for web forms. A separate type `ish-captcha-field` is available for the reCAPTCHA functionality. +- The three components (_registration-page.component.ts_, _request-reminder-form.component.ts_, _contact-form.component.ts_) contain Formly forms that contain a field of type CAPTCHA. +- When these forms are submitted, the CAPTCHA token is set as a header parameter for the subsequent REST request. (When using CAPTCHA V3, the CAPTCHA action is also set as a header parameter). +- The CAPTCHA validation takes place on the ICM side. If the score exceeds the threshold configured in the managed service, the request is rejected. + +## Integration of the CAPTCHA Components + +There is a special CAPTCHA field type that makes it very easy to protect any web form using the CAPTCHA functionality. +The following example shows how to add a CAPTCHA field of type `ish-captcha-field` to any Formly form. + +The component's TypeScript file looks like this: + +```ts +export class ExamplePageComponent implements OnInit { + ... + fields: FormlyFieldConfig[]; + exampleFormGroup = new UntypedFormGroup({}); + ... + ngOnInit() { + ... + this.fields = [ + ... + { + type: 'ish-captcha-field', + props: { + topic: 'forgotPassword', + }, + }, + ]; + } + ... +} +``` + +The `ish-captcha-field` type is registered in the _src.app.shared.formly.types.**types.module.ts**_. +Additionally, the module binds the corresponding field component to this type. +The topic to be set corresponds to the CAPTCHA channel preferences available in ICM. +If these preferences are disabled in ICM, CAPTCHA validation for this topic is also disabled in the PWA. + +The following table shows the mapping between existing PWA CAPTCHA topic names and the CAPTCHA channel preferences in the ICM: + +| **CaptchaTopic** | **ICM Settings** | +| ------------------------------------ | --------------------------------------- | +| contactUs | Contact Us | +| emailShoppingCart | E-mail Shopping Cart | +| forgotPassword | Forgot password | +| redemptionOfGiftCardsAndCertificates | Redemption of Gift Cards & Certificates | +| register | Registration | + +> :exclamation: The topic value is also appended to the header of the request triggered by submitting the web form. This is necessary to support the [actions concept of Google reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3#actions). + +## Basic Components + +As you can see in the high-level overview, there are components that represent the actual CAPTCHA functionality. +Depending on the version used, these components implement either widgets provided by Google or the reCAPTCHA token functionality. +The [ng-recaptcha](https://github.com/DethAriel/ng-recaptcha) library was used for the implementation. + +### CAPTCHA V2 (captcha-v2.component.ts) + +To add the widgets provided by Google, you need to import the **ReCAPTCHAModule** of the _ng-recaptcha_ library. +Furthermore, _captcha site key_ must be set to initialize the widget. +This site key is a required option on the reCAPTCHA HTML element. +It is also necessary to store the token determined by the CAPTCHA event as a form control parameter. +This allows the response to be validated and handled accordingly. +In case of an error, an error message is displayed; in case of success, the entire form is processed further. + +### CAPTCHA V3 (captcha-v3.component.ts) + +To implement a callback function to handle the token, you need to import the **RecaptchaV3Module** of the _ng-recaptcha_ library. +This component will trigger a callback every two minutes to retrieve a current reCAPTCHA token. +This token is then appended to the request triggered by the submitted form. +The ICM backend will then validate the token. + +## Further References + +- [Intershop Knowledge Base | Concept - ReCaptcha v3](https://support.intershop.com/kb/index.php/Display/29X281) +- [Intershop Knowledge Base | Concept - ReCaptcha v2](https://support.intershop.com/kb/index.php/Display/2794B3) +- [Getting Started | Formly](https://formly.dev/docs/guide/getting-started) +- [GitHub - DethAriel/ng-recaptcha: Angular component for Google reCAPTCHA](https://github.com/DethAriel/ng-recaptcha) +- [Developer's Guide | reCAPTCHA | Google for Developers](https://developers.google.com/recaptcha/intro) From 65c8c9a44a52c7e1c52f431cbf59f792227b9bee Mon Sep 17 00:00:00 2001 From: Stefan Hauke Date: Wed, 5 Jul 2023 08:14:30 +0200 Subject: [PATCH 04/46] perf: remove 3 sec SSR delay by enabling PreviewContextID listening only in browser (#1455) * PreviewContextID is not used in SSR so it should only be enabled for the browser/client mode * `timer(3000)` resulted in a general 3 seconds rendering time for all SSR requests --- .../core/services/preview/preview.service.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/app/core/services/preview/preview.service.ts b/src/app/core/services/preview/preview.service.ts index ceea961eb3..1c5206a2fb 100644 --- a/src/app/core/services/preview/preview.service.ts +++ b/src/app/core/services/preview/preview.service.ts @@ -34,19 +34,21 @@ export class PreviewService { private route: ActivatedRoute ) { this.init(); - race([ - this.route.queryParams.pipe( - filter(params => params.PreviewContextID), - map(params => params.PreviewContextID), - take(1) - ), - // end listening for PreviewContextID if there is no such parameter at initialization - timer(3000), - ]).subscribe(value => { - if (!this.previewContextId && value) { - this.previewContextId = value; - } - }); + if (!SSR) { + race([ + this.route.queryParams.pipe( + filter(params => params.PreviewContextID), + map(params => params.PreviewContextID), + take(1) + ), + // end listening for PreviewContextID if there is no such parameter at initialization + timer(3000), + ]).subscribe(value => { + if (!this.previewContextId && value) { + this.previewContextId = value; + } + }); + } } /** From be31fef6009b215f67308bf7cc8c8ea802a3c008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silke=20Gr=C3=BCber?= Date: Wed, 5 Jul 2023 08:35:32 +0200 Subject: [PATCH 05/46] fix: display updated customer address on the myAccount address page (#1454) * save updated customer address in the ngrx store * clear region on customer address update if necessary --- src/app/core/store/customer/addresses/addresses.effects.spec.ts | 2 +- src/app/core/store/customer/addresses/addresses.effects.ts | 2 +- .../formly-customer-address-form.component.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/store/customer/addresses/addresses.effects.spec.ts b/src/app/core/store/customer/addresses/addresses.effects.spec.ts index 46597c97c0..a927d038c1 100644 --- a/src/app/core/store/customer/addresses/addresses.effects.spec.ts +++ b/src/app/core/store/customer/addresses/addresses.effects.spec.ts @@ -173,7 +173,7 @@ describe('Addresses Effects', () => { }); it('should map to action of type updateCustomerSuccess', () => { - const address = { urn: '123' } as Address; + const address = { urn: 'test' } as Address; const action = updateCustomerAddress({ address }); const completion = updateCustomerAddressSuccess({ address }); const completion2 = displaySuccessMessage({ diff --git a/src/app/core/store/customer/addresses/addresses.effects.ts b/src/app/core/store/customer/addresses/addresses.effects.ts index e47fc4c30e..6f35924e8a 100644 --- a/src/app/core/store/customer/addresses/addresses.effects.ts +++ b/src/app/core/store/customer/addresses/addresses.effects.ts @@ -73,7 +73,7 @@ export class AddressesEffects { filter(([address, customer]) => !!address || !!customer), mergeMap(([address]) => this.addressService.updateCustomerAddress('-', address).pipe( - mergeMap(() => [ + mergeMap(address => [ updateCustomerAddressSuccess({ address }), displaySuccessMessage({ message: 'account.addresses.address_updated.message' }), ]), diff --git a/src/app/shared/formly-address-forms/components/formly-customer-address-form/formly-customer-address-form.component.ts b/src/app/shared/formly-address-forms/components/formly-customer-address-form/formly-customer-address-form.component.ts index 7650d1220e..c7d227ff70 100644 --- a/src/app/shared/formly-address-forms/components/formly-customer-address-form/formly-customer-address-form.component.ts +++ b/src/app/shared/formly-address-forms/components/formly-customer-address-form/formly-customer-address-form.component.ts @@ -112,7 +112,7 @@ export class FormlyCustomerAddressFormComponent implements OnInit, OnChanges { let formAddress: Address = this.form.value.address; if (this.address) { // update form values in the original address - formAddress = { ...this.address, ...formAddress }; + formAddress = { ...this.address, mainDivisionCode: '', ...formAddress }; } if (this.extension) { formAddress = { ...formAddress, email: this.extensionForm.get('email')?.value }; From 31028be76ed05efd3ea16ea7038cc606e804b323 Mon Sep 17 00:00:00 2001 From: Marcel Eisentraut Date: Fri, 7 Jul 2023 16:19:59 +0200 Subject: [PATCH 06/46] fix: /localization call should wait until all server configurations are applied (#1457) --- .../utils/translate/icm-translate-loader.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/app/core/utils/translate/icm-translate-loader.ts b/src/app/core/utils/translate/icm-translate-loader.ts index ceda774fc9..7790eecb06 100644 --- a/src/app/core/utils/translate/icm-translate-loader.ts +++ b/src/app/core/utils/translate/icm-translate-loader.ts @@ -1,9 +1,11 @@ import { Inject, Injectable, InjectionToken } from '@angular/core'; import { TransferState, makeStateKey } from '@angular/platform-browser'; +import { Actions, ofType } from '@ngrx/effects'; +import { routerNavigationAction } from '@ngrx/router-store'; import { TranslateLoader } from '@ngx-translate/core'; import { memoize } from 'lodash-es'; import { combineLatest, defer, from, iif, of } from 'rxjs'; -import { catchError, map, shareReplay, tap } from 'rxjs/operators'; +import { catchError, first, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { LocalizationsService } from 'ish-core/services/localizations/localizations.service'; import { InjectSingle } from 'ish-core/utils/injection'; @@ -20,6 +22,7 @@ export const LOCAL_TRANSLATIONS = new InjectionToken('transla @Injectable() export class ICMTranslateLoader implements TranslateLoader { constructor( + private actions$: Actions, private transferState: TransferState, private localizations: LocalizationsService, @Inject(LOCAL_TRANSLATIONS) private localTranslations: InjectSingle @@ -33,10 +36,17 @@ export class ICMTranslateLoader implements TranslateLoader { const server$ = iif( () => !SSR && this.transferState.hasKey(SSR_TRANSLATIONS), of(this.transferState.get(SSR_TRANSLATIONS, {})), - this.localizations.getServerTranslations(lang).pipe( - tap(data => { - this.transferState.set(SSR_TRANSLATIONS, data); - }) + // the localization call should wait until all server supplied configuration parameter are applied to the state + this.actions$.pipe( + ofType(routerNavigationAction), + first(), + switchMap(() => + this.localizations.getServerTranslations(lang).pipe( + tap(data => { + this.transferState.set(SSR_TRANSLATIONS, data); + }) + ) + ) ) ); return combineLatest([local$, server$]).pipe( From 623921d0c8d006a554a267b7e5ff2438b6e0fe36 Mon Sep 17 00:00:00 2001 From: Krishna Pal Singh Date: Mon, 10 Jul 2023 12:02:30 +0530 Subject: [PATCH 07/46] fix: NoSunday date validator does not work properly (#1459) Update special-validators.ts for sunday, it's not working due to wrong check. --- src/app/shared/forms/validators/special-validators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/forms/validators/special-validators.ts b/src/app/shared/forms/validators/special-validators.ts index 9116dfb882..d61dd228f8 100644 --- a/src/app/shared/forms/validators/special-validators.ts +++ b/src/app/shared/forms/validators/special-validators.ts @@ -149,6 +149,6 @@ export class SpecialValidators { private static noDay(control: FormControl, day: 'saturday' | 'sunday'): boolean { const date = control.value as Date; - return !(day === 'saturday' ? date?.getDay() === 6 : date?.getDate() === 0); + return !(day === 'saturday' ? date?.getDay() === 6 : date?.getDay() === 0); } } From 2c79d9704ab8644d5c57c7b1370cd2ea2a718b41 Mon Sep 17 00:00:00 2001 From: AMenzelInterShop <141016038+AMenzelInterShop@users.noreply.github.com> Date: Mon, 7 Aug 2023 09:55:35 +0200 Subject: [PATCH 08/46] i18n: adjusted french and german translations (#1470) --- src/assets/i18n/de_DE.json | 8 ++++---- src/assets/i18n/fr_FR.json | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/assets/i18n/de_DE.json b/src/assets/i18n/de_DE.json index 0d8a87baf1..d6c36b37d1 100644 --- a/src/assets/i18n/de_DE.json +++ b/src/assets/i18n/de_DE.json @@ -554,10 +554,10 @@ "address.doctor.suggestion.proposals": "Vorschläge", "address.doctor.suggestion.text": "

Wir haben Vorschläge für Ihre eingegebene Adresse. Bitte wählen Sie die zu verwendende Adresse aus.

", "address.doctor.suggestion.title": "Vorschläge für Ihre Adresse", - "application.recentlyViewed.breadcrumb.label": "Ihre Browsing-Historie", - "application.recentlyViewed.clear": "Historie löschen", - "application.recentlyViewed.empty": "Ihre Browsing-Historie ist leer", - "application.recentlyViewed.heading": "Ihre Browsing-Historie", + "application.recentlyViewed.breadcrumb.label": "Ihr Browserverlauf", + "application.recentlyViewed.clear": "Browserverlauf löschen", + "application.recentlyViewed.empty": "Ihr Browserverlauf ist leer", + "application.recentlyViewed.heading": "Ihr Browserverlauf", "application.recentlyViewed.product.heading": "Zuletzt betrachtete Produkte", "approval.cart.approval_required": "Genehmigung erforderlich", "approval.cart.link.details": "Details", diff --git a/src/assets/i18n/fr_FR.json b/src/assets/i18n/fr_FR.json index 971912d3ad..d34ee7b8f0 100644 --- a/src/assets/i18n/fr_FR.json +++ b/src/assets/i18n/fr_FR.json @@ -172,7 +172,7 @@ "account.login.notifications.message": "Veuillez vous connecter à votre compte pour utiliser l’outil de notification.", "account.login.orders.message": "Veuillez vous connecter à votre compte pour voir les commandes.", "account.login.ordertemplates.message": "Veuillez vous connecter à votre compte pour utiliser les modèles de commande.", - "account.login.password.error.required": "Veuillez entrer un mot de passe.", + "account.login.password.error.required": "Veuillez saisir un mot de passe.", "account.login.password.label": "Mot de passe", "account.login.productnotification.message": "Veuillez vous connecter à votre compte pour créer une notification.", "account.login.profile_settings.message": "Veuillez vous connecter à votre compte pour mettre à jour les paramètres de profil.", @@ -182,7 +182,7 @@ "account.login.session_timeout.message": "Vous avez été déconnecté automatiquement en raison d’une longue période d’inactivité. Veuillez vous reconnecter pour continuer vos achats.", "account.login.signin.heading": "Se connecter", "account.login.tacton.message": "Veuillez vous connecter à votre compte pour configurer les produits.", - "account.login.username.error.required": "Veuillez entrer un nom d’utilisateur.", + "account.login.username.error.required": "Veuillez saisir un nom d’utilisateur.", "account.login.username.label": "Nom d’utilisateur", "account.login.username_password.error.invalid": "Votre combinaison nom d’utilisateur / mot de passe est incorrecte. Veuillez réessayer.", "account.login.wishlists.message": "Veuillez vous connecter à votre compte pour utiliser les listes de souhaits.", @@ -198,7 +198,7 @@ "account.new_user.heading": "Nouveaux utilisateurs", "account.notifications.backinstock.heading": "Retour en stock", "account.notifications.breadcrumb_link": "Notifications", - "account.notifications.heading": "Notifications de produits", + "account.notifications.heading": "Notifications de produit", "account.notifications.link": "Notifications", "account.notifications.no_items_message": "Il n’y a actuellement aucun produit à surveiller.", "account.notifications.price.heading": "Changements de prix", @@ -208,7 +208,7 @@ "account.notifications.table.product": "Produit", "account.option.select.text": "Veuillez sélectionner", "account.order.most_recent.heading": "Les commandes les plus récentes", - "account.order.questions.note": "Merci de consulter la section Aide de notre site Web pour des renseignements détaillés sur votre commande et l’expédition ou Contactez-nous 24 heures sur 24.", + "account.order.questions.note": "Veuillez consulter la section Aide de notre site Web pour des renseignements détaillés sur votre commande et l’expédition ou Contactez-nous 24 heures sur 24.", "account.order.questions.title": "Avez-vous des questions supplémentaires ?", "account.order.subtitle": "La commande la plus récente apparaît en premier. Veuillez prévoir jusqu’à 5 minutes pour que les nouvelles commandes apparaissent ci-dessous.", "account.order.view_all_order.link": "Afficher toutes les commandes", @@ -321,25 +321,25 @@ "account.profile.phone.label": "Téléphone", "account.profile.update.link": "Modifier", "account.profile.update_company_profile.message": "Vos informations de l’entreprise ont été mises à jour.", - "account.profile.update_email.message": "Votre adresse mail a été mise à jour. Nous avons envoyé un mail à {{0}} pour confirmer.", + "account.profile.update_email.message": "Votre adresse courriel a été mise à jour. Nous avons envoyé un mail à {{0}} pour confirmer.", "account.profile.update_password.message": "Votre mot de passe a été mis à jour.", "account.profile.update_profile.message": "Les données de votre profil ont été mises à jour.", "account.punchout.configuration.button.label": "Configurer", "account.punchout.configuration.description": "Spécifiez les valeurs de retour du format de transfert OCI Punchout pour votre système d’approvisionnement.", "account.punchout.configuration.form.add_row.link": "Ajouter une ligne", - "account.punchout.configuration.form.heading.attribute": "Paramètre Nom", + "account.punchout.configuration.form.heading.attribute": "Nom du paramètre", "account.punchout.configuration.form.heading.format": "Format", "account.punchout.configuration.form.heading.mapping": "Mappage", - "account.punchout.configuration.form.heading.transformed-attribute": "Paramètre Valeur", + "account.punchout.configuration.form.heading.transformed-attribute": "Valeur du paramètre", "account.punchout.configuration.form.mapping.from.error": "La valeur «Mappage de» est manquante.", "account.punchout.configuration.form.mapping.to.error": "La valeur «Mappage à» est manquante.", - "account.punchout.configuration.form.tooltip.mapping.content": "Le mappage des données permet d’attribuer des valeurs individuellement à la configuration. Les valeurs des champs sont mappées à partir du catalogue externe de pointage externe vers votre système d’approvisionnement. Veuillez noter ce qui suit:
  • Une règle de mappage ne peut être saisie que pour les champs ayant une valeur de paramètre.
  • Une règle de mappage doit toujours avoir une valeur « de » et une valeur « à », sinon elle ne sera pas sauvegardée.
  • Si plusieurs règles de mappage existent, seule la première règle correspondante sera appliquée. Les règles de chaînage ne sont pas possibles.
  • Une règle n’est appliquée qu’une fois dans un string.
  • Un astérisque (*) correspond à un nombre quelconque de caractères, par exemple 2345* = 23450000.
", + "account.punchout.configuration.form.tooltip.mapping.content": "Le mappage des données permet d’attribuer des valeurs individuellement à la configuration. Les valeurs des champs sont mappées à partir du catalogue punchout externe vers votre système d’approvisionnement. Veuillez noter ce qui suit:
  • Une règle de mappage ne peut être saisie que pour les champs ayant une valeur de paramètre.
  • Une règle de mappage doit toujours avoir une valeur « de » et une valeur « à », sinon elle ne sera pas sauvegardée.
  • Si plusieurs règles de mappage existent, seule la première règle correspondante sera appliquée. Les règles de chaînage ne sont pas possibles.
  • Une règle n’est appliquée qu’une fois dans un string.
  • Un astérisque (*) correspond à un nombre quelconque de caractères, par exemple 2345* = 23450000.
", "account.punchout.configuration.form.tooltip.mapping.headline": "Mappage des données", - "account.punchout.configuration.form.tooltip.placeholder.headline": "Espaces réservés disponibles", + "account.punchout.configuration.form.tooltip.placeholder.headline": "Emplacements disponibles", "account.punchout.configuration.heading": "Configuration punchout", "account.punchout.configuration.link": "Configuration", "account.punchout.configuration.option.none.label": "Aucun", - "account.punchout.configuration.save_success.message": "La configuration du pointage de l’OCI a été sauvegardée.", + "account.punchout.configuration.save_success.message": "La configuration de l’OCI punchout a été sauvegardée.", "account.punchout.create.description": "Veuillez indiquer votre utilisateur avec vos données d’identification {{0, translate, account.punchout.type.text}} punchout.", "account.punchout.create.heading": "Créer un nouvel utilisateur {{0, translate, account.punchout.type.text}} punchout", "account.punchout.create.link": "Créer un nouvel utilisateur \"Punchout\"", @@ -543,10 +543,10 @@ "account.wishlists.widget.heading": "Articles sur votre liste de souhaits", "account.wishlists.wishlist_form.cancel_button.text": "Annuler", "account.wishlists.wishlist_form.name.error.maxlength": "Le nom de la liste de souhaits ne doit pas dépasser 35 caractères. ", - "account.wishlists.wishlist_form.name.error.required": "Veuillez entrer un nom pour la liste de souhaits.", + "account.wishlists.wishlist_form.name.error.required": "Veuillez saisir un nom pour la liste de souhaits.", "account.wishlists.wishlist_form.name.label": "Nom de la liste de souhaits", "account.wishlists.wishlist_form.preferred.label": "Liste préférée", - "account.wishlists.wishlist_form.preferred.tooltip.content": "La sélection fera de la liste de souhaits votre liste de souhaits préférée et remettra tous les autres (s’il y en a) au statut normal. Tous les produits ajoutés seront ajoutés par défaut à cette liste de souhaits.", + "account.wishlists.wishlist_form.preferred.tooltip.content": "La sélection marquera la liste de souhaits comme liste préférée et ramenera tous les autres (s’il y en a) au statut normal. Tous les produits ajoutés seront ajoutés par défaut à cette liste de souhaits.", "account.wishlists.wishlist_form.preferred.tooltip.headline": "Définir comme préférence", "account.wishlists.wishlist_form.preferred.tooltip.linktext": "Détails", "address.doctor.suggestion.address": "Adresse actuelle", From 30cc36cf62d6cf2127d4423881d67923060007b8 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Fri, 11 Aug 2023 08:36:35 +0200 Subject: [PATCH 09/46] fix: repair partial build with "active-themes" after npm upgrade (#1476) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 006b358097..e802e9498a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ COPY templates/webpack/* /workspace/templates/webpack/ ARG testing=false ENV TESTING=${testing} ARG activeThemes= -RUN if [ ! -z "${activeThemes}" ]; then npm config set active-themes="${activeThemes}"; fi +RUN if [ ! -z "${activeThemes}" ]; then npm pkg set config.active-themes="${activeThemes}"; fi RUN npm run build:multi client -- --deploy-url=DEPLOY_URL_PLACEHOLDER COPY tsconfig.server.json server.ts /workspace/ RUN npm run build:multi server From 721b26a13d2d337ca58a7b9548ca9e2c0656ff3c Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Fri, 11 Aug 2023 10:08:42 +0200 Subject: [PATCH 10/46] fix: repair caching for output-hashed files (#1473) --- server.ts | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/server.ts b/server.ts index 67b3ce9a5d..0a1ddb7ece 100644 --- a/server.ts +++ b/server.ts @@ -304,6 +304,16 @@ export function app() { server.use('/INTERSHOP', icmProxy); } + function defaultCacheControl(path: string): string { + if (/\.[0-9a-f]{16,}\./.test(path)) { + // file was output-hashed -> 1y + return 'public, max-age=31557600'; + } else { + // file should be re-checked more frequently -> 5m + return 'public, max-age=300'; + } + } + const SOURCE_MAPS_ACTIVE = /on|1|true|yes/.test(process.env.SOURCE_MAPS?.toLowerCase()); if (SOURCE_MAPS_ACTIVE) { console.warn('SOURCE_MAPS are active - never use this in production!'); @@ -312,7 +322,11 @@ export function app() { // Serve static files from browser folder server.get(/\/.*\.js\.map$/, (req, res, next) => { if (SOURCE_MAPS_ACTIVE) { - return express.static(BROWSER_FOLDER)(req, res, next); + return express.static(BROWSER_FOLDER, { + setHeaders: (res, path) => { + res.set('Cache-Control', defaultCacheControl(path)); + }, + })(req, res, next); } else { return res.sendStatus(404); } @@ -327,6 +341,7 @@ export function app() { res.sendStatus(404); } else { res.set('Content-Type', `${path.endsWith('css') ? 'text/css' : 'application/javascript'}; charset=UTF-8`); + res.set('Cache-Control', defaultCacheControl(path)); res.send(setDeployUrlInFile(DEPLOY_URL, path, data)); } }); @@ -352,13 +367,7 @@ export function app() { '*.*', express.static(BROWSER_FOLDER, { setHeaders: (res, path) => { - if (/\.[0-9a-f]{20,}\./.test(path)) { - // file was output-hashed -> 1y - res.set('Cache-Control', 'public, max-age=31557600'); - } else { - // file should be re-checked more frequently -> 5m - res.set('Cache-Control', 'public, max-age=300'); - } + res.set('Cache-Control', defaultCacheControl(path)); // add cors headers for required resources if ( DEPLOY_URL.startsWith('http') && From 23f7585ccc657e896a5733341e160e7d54946aa3 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Fri, 11 Aug 2023 12:37:24 +0200 Subject: [PATCH 11/46] chore: reactivate curly rule (#1478) --- .eslintrc.json | 1 + e2e/cypress-ci-e2e.ts | 16 ++++++++++++---- .../src/rules/sort-testbed-metadata-arrays.ts | 4 +++- schematics/src/utils/lint-fix.ts | 4 +++- scripts/find-dead-code.ts | 8 ++++++-- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3f5029ac43..8698ff1d3a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -179,6 +179,7 @@ "max": 10 } ], + "curly": "warn", "dot-notation": "off", // disabled in favor of @typescript-eslint/dot-notation "eqeqeq": ["error", "always"], "etc/no-commented-out-code": "warn", diff --git a/e2e/cypress-ci-e2e.ts b/e2e/cypress-ci-e2e.ts index 15b1e8491a..83a97af1e5 100644 --- a/e2e/cypress-ci-e2e.ts +++ b/e2e/cypress-ci-e2e.ts @@ -71,10 +71,18 @@ const run = ( config = { ...config, env: { ...config.env, numRuns: num } }; // activate video only for last run - if (num >= MAX_NUM_RUNS) config.config.video = true; - if (num > 1) config.config.trashAssetsBeforeRuns = false; - if (spec) config.spec = spec; - if (retryGroup) config.group = retryGroup; + if (num >= MAX_NUM_RUNS) { + config.config.video = true; + } + if (num > 1) { + config.config.trashAssetsBeforeRuns = false; + } + if (spec) { + config.spec = spec; + } + if (retryGroup) { + config.group = retryGroup; + } console.log(config); diff --git a/eslint-rules/src/rules/sort-testbed-metadata-arrays.ts b/eslint-rules/src/rules/sort-testbed-metadata-arrays.ts index cf0ad67ec6..c6fb1d994d 100644 --- a/eslint-rules/src/rules/sort-testbed-metadata-arrays.ts +++ b/eslint-rules/src/rules/sort-testbed-metadata-arrays.ts @@ -33,7 +33,9 @@ const sortTestbedMetadataArraysRule: TSESLint.RuleModule .map((current, index, list) => [current, list[index + 1]]) .find(([current, next]) => next && getText(current).localeCompare(getText(next)) === 1); - if (!unorderedNodes) return; + if (!unorderedNodes) { + return; + } const [unorderedNode, nextNode] = unorderedNodes; context.report({ diff --git a/schematics/src/utils/lint-fix.ts b/schematics/src/utils/lint-fix.ts index 3a7e85f936..7d6bc0e383 100644 --- a/schematics/src/utils/lint-fix.ts +++ b/schematics/src/utils/lint-fix.ts @@ -33,7 +33,9 @@ export function applyLintFix(): Rule { .map(action => action.path.substring(1)) .filter(path => path.endsWith('.ts') || path.endsWith('.html')) .forEach(file => { - if (!lintFiles.includes(file)) lintFiles.push(file); + if (!lintFiles.includes(file)) { + lintFiles.push(file); + } }); registerLintAtEnd(findProjectRoot()); diff --git a/scripts/find-dead-code.ts b/scripts/find-dead-code.ts index 9c7a8dfb4e..43bdac5bec 100644 --- a/scripts/find-dead-code.ts +++ b/scripts/find-dead-code.ts @@ -146,13 +146,17 @@ function checkNode(node: Node) { .getLeadingCommentRanges() .some(c => c.getText().includes('not-dead-code')) ) { - if (process.env.DEBUG) console.warn('ignoring (1)', node.getText()); + if (process.env.DEBUG) { + console.warn('ignoring (1)', node.getText()); + } return; } const ignoreComment = node.getPreviousSiblingIfKind(SyntaxKind.SingleLineCommentTrivia); if (ignoreComment?.getText().includes('not-dead-code')) { - if (process.env.DEBUG) console.warn('ignoring (2)', node.getText()); + if (process.env.DEBUG) { + console.warn('ignoring (2)', node.getText()); + } return; } From 96737be6edc32c9f6fa8b0cae2093a7e9d7918f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silke=20Gr=C3=BCber?= Date: Fri, 11 Aug 2023 14:18:08 +0200 Subject: [PATCH 12/46] fix: display first product image after switching products on product detail page (#1474) --- .../product-images/product-images.component.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/app/pages/product/product-images/product-images.component.ts b/src/app/pages/product/product-images/product-images.component.ts index 5ec47b995c..e82be04757 100644 --- a/src/app/pages/product/product-images/product-images.component.ts +++ b/src/app/pages/product/product-images/product-images.component.ts @@ -1,12 +1,13 @@ import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import { Observable } from 'rxjs'; -import { map, shareReplay } from 'rxjs/operators'; +import { distinctUntilKeyChanged, map, shareReplay, tap } from 'rxjs/operators'; import SwiperCore, { Navigation } from 'swiper'; import { SwiperComponent } from 'swiper/angular'; import { ProductContextFacade } from 'ish-core/facades/product-context.facade'; import { ProductView } from 'ish-core/models/product-view/product-view.model'; import { ProductHelper } from 'ish-core/models/product/product.model'; +import { whenTruthy } from 'ish-core/utils/operators'; import { ModalDialogComponent } from 'ish-shared/components/common/modal-dialog/modal-dialog.component'; SwiperCore.use([Navigation]); @@ -36,7 +37,16 @@ export class ProductImagesComponent implements OnInit { constructor(private context: ProductContextFacade) {} ngOnInit() { - this.product$ = this.context.select('product'); + this.product$ = this.context.select('product').pipe( + whenTruthy(), + distinctUntilKeyChanged('sku'), + tap(() => { + if (this.carousel?.swiperRef?.activeIndex) { + this.setActiveSlide(0); + } + }), + shareReplay(1) + ); this.zoomImageIds$ = this.getImageViewIDs$('ZOOM').pipe(shareReplay(1)); } From d0984ce3c2bca4e5b795a5995fab386823e4f143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silke=20Gr=C3=BCber?= Date: Mon, 14 Aug 2023 18:59:12 +0200 Subject: [PATCH 13/46] fix: reload the application after the pgid changed (#1461) fix: reload the application after an error occurred due to a changed pgid --- src/app/core/core.module.ts | 2 ++ .../interceptors/pgid-change.interceptor.ts | 25 +++++++++++++++++++ .../core/utils/api-token/api-token.service.ts | 4 ++- 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/app/core/interceptors/pgid-change.interceptor.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index a8c30cdaf9..62416b3801 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -12,6 +12,7 @@ import { ICMErrorMapperInterceptor } from './interceptors/icm-error-mapper.inter import { IdentityProviderInterceptor } from './interceptors/identity-provider.interceptor'; import { MockInterceptor } from './interceptors/mock.interceptor'; import { PaymentPayoneInterceptor } from './interceptors/payment-payone.interceptor'; +import { PGIDChangeInterceptor } from './interceptors/pgid-change.interceptor'; import { PreviewInterceptor } from './interceptors/preview.interceptor'; import { InternationalizationModule } from './internationalization.module'; import { StateManagementModule } from './state-management.module'; @@ -29,6 +30,7 @@ import { DefaultErrorHandler } from './utils/default-error-handler'; StateManagementModule, ], providers: [ + { provide: HTTP_INTERCEPTORS, useClass: PGIDChangeInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: ICMErrorMapperInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/interceptors/pgid-change.interceptor.ts b/src/app/core/interceptors/pgid-change.interceptor.ts new file mode 100644 index 0000000000..c528602c6d --- /dev/null +++ b/src/app/core/interceptors/pgid-change.interceptor.ts @@ -0,0 +1,25 @@ +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, catchError, throwError } from 'rxjs'; + +/* eslint-disable @typescript-eslint/ban-types */ + +/** + * reload page when the pgid of the user has changed + */ +@Injectable() +export class PGIDChangeInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return next.handle(req).pipe( + catchError((error: HttpErrorResponse) => { + if ( + error.name === 'HttpErrorResponse' && + error.message === 'Bad Request (The provided matrix parameter "pgid" does not match the authenticated user.)' + ) { + return throwError(() => window.location.reload()); + } + return throwError(() => error); + }) + ); + } +} diff --git a/src/app/core/utils/api-token/api-token.service.ts b/src/app/core/utils/api-token/api-token.service.ts index 637de8fdb2..5e6abcdc35 100644 --- a/src/app/core/utils/api-token/api-token.service.ts +++ b/src/app/core/utils/api-token/api-token.service.ts @@ -271,7 +271,9 @@ export class ApiTokenService { private isAuthTokenError(err: unknown) { return ( - err instanceof HttpErrorResponse && typeof err.error === 'string' && err.error?.toLowerCase().includes('token') + err instanceof HttpErrorResponse && + typeof err.error === 'string' && + (err.error.includes('AuthenticationToken') || err.error.includes('Unable to decode token')) ); } From 2b9d8eabc666e2899fd5d3a31de8db022fe2314e Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Tue, 15 Aug 2023 09:45:52 +0200 Subject: [PATCH 14/46] fix: properly handle system signals in pwa docker container (#1471) --- Dockerfile | 3 ++- dist/entrypoint.sh | 8 ++------ docker-compose.yml | 2 ++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index e802e9498a..f2bb0765b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,7 @@ RUN node scripts/compile-docker-scripts COPY dist/* /workspace/dist/ FROM node:18.16.0-alpine +RUN apk add --no-cache tini COPY --from=buildstep /workspace/dist /dist RUN cd dist && npm install ARG displayVersion= @@ -37,4 +38,4 @@ EXPOSE 4200 9113 RUN mkdir /.pm2 && chmod 777 -Rf /.pm2 && touch /dist/ecosystem.yml && chmod 777 -f /dist/ecosystem.yml USER nobody HEALTHCHECK --interval=60s --timeout=20s --start-period=2s CMD node /dist/healthcheck.js -ENTRYPOINT ["sh","/dist/entrypoint.sh"] +ENTRYPOINT [ "/sbin/tini", "--", "sh", "/dist/entrypoint.sh" ] diff --git a/dist/entrypoint.sh b/dist/entrypoint.sh index a5db6f02d6..9de9426e88 100644 --- a/dist/entrypoint.sh +++ b/dist/entrypoint.sh @@ -2,18 +2,14 @@ set -e -trap 'echo recieved INT; exit 1' SIGINT -trap 'echo recieved TERM; exit 0' SIGTERM -trap 'echo recieved KILL; exit 1' SIGKILL - if [ -z "$*" ] then - # use 'node dist//run-standalone' + # use 'exec node dist//run-standalone' # instead of pm2 to fallback to running # a single theme only node dist/build-ecosystem.js - pm2-runtime dist/ecosystem.yml + exec pm2-runtime dist/ecosystem.yml else exec "$@" fi diff --git a/docker-compose.yml b/docker-compose.yml index 8ddbe2563b..2cf0af67a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,8 @@ services: serviceWorker: 'false' # activeThemes: b2b # activeThemes: b2c + # command: ['node', 'dist/b2c/run-standalone'] + # command: ['node', 'dist/b2b/run-standalone'] environment: # ICM_BASE_URL: LOGGING: 'true' From 28ba5f0bbb2a9ed20e96a041531f020d7e0f672a Mon Sep 17 00:00:00 2001 From: Andreas Steinmann Date: Tue, 15 Aug 2023 19:29:07 +0200 Subject: [PATCH 15/46] fix: remove interfering query parameter "page" for product detail page routes (#1481) --- .../product/product-image/product-image.component.html | 1 + .../components/product/product-name/product-name.component.html | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/shared/components/product/product-image/product-image.component.html b/src/app/shared/components/product/product-image/product-image.component.html index ff1a9e736a..36a7e0e292 100644 --- a/src/app/shared/components/product/product-image/product-image.component.html +++ b/src/app/shared/components/product/product-image/product-image.component.html @@ -4,6 +4,7 @@ data-testing-id="product-image-link" [routerLink]="linkTarget || (productURL$ | async)" [queryParamsHandling]="computedQueryParamsHandling" + [queryParams]="{ page: null }" > diff --git a/src/app/shared/components/product/product-name/product-name.component.html b/src/app/shared/components/product/product-name/product-name.component.html index 14fd7cc2bb..ba96778409 100644 --- a/src/app/shared/components/product/product-name/product-name.component.html +++ b/src/app/shared/components/product/product-name/product-name.component.html @@ -6,6 +6,7 @@ data-testing-id="product-name-link" [routerLink]="productURL$ | async" [queryParamsHandling]="computedQueryParamsHandling" + [queryParams]="{ page: null }" >{{ alternate || productName }} From 4724fdaaced662bec7794d5c117bf2b4a4441044 Mon Sep 17 00:00:00 2001 From: Marcel Eisentraut Date: Thu, 6 Jul 2023 13:42:14 +0200 Subject: [PATCH 16/46] fix: complete current localization call, when a new language is requested --- src/app/core/utils/translate/icm-translate-loader.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/core/utils/translate/icm-translate-loader.ts b/src/app/core/utils/translate/icm-translate-loader.ts index 7790eecb06..bd07529d1f 100644 --- a/src/app/core/utils/translate/icm-translate-loader.ts +++ b/src/app/core/utils/translate/icm-translate-loader.ts @@ -4,8 +4,8 @@ import { Actions, ofType } from '@ngrx/effects'; import { routerNavigationAction } from '@ngrx/router-store'; import { TranslateLoader } from '@ngx-translate/core'; import { memoize } from 'lodash-es'; -import { combineLatest, defer, from, iif, of } from 'rxjs'; -import { catchError, first, map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { Subject, combineLatest, defer, from, iif, of } from 'rxjs'; +import { catchError, filter, first, map, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators'; import { LocalizationsService } from 'ish-core/services/localizations/localizations.service'; import { InjectSingle } from 'ish-core/utils/injection'; @@ -21,6 +21,8 @@ export const LOCAL_TRANSLATIONS = new InjectionToken('transla @Injectable() export class ICMTranslateLoader implements TranslateLoader { + newLanguage$ = new Subject(); + constructor( private actions$: Actions, private transferState: TransferState, @@ -29,6 +31,7 @@ export class ICMTranslateLoader implements TranslateLoader { ) {} getTranslation = memoize(lang => { + this.newLanguage$.next(lang); const SSR_TRANSLATIONS = makeStateKey(`ssrTranslations-${lang}`); const local$ = defer(() => from(this.localTranslations.useFactory(lang)).pipe(catchError(() => of({})))); @@ -46,7 +49,9 @@ export class ICMTranslateLoader implements TranslateLoader { this.transferState.set(SSR_TRANSLATIONS, data); }) ) - ) + ), + // close current localization call when a new language is requested + takeUntil(this.newLanguage$.pipe(filter(newLang => newLang !== lang))) ) ); return combineLatest([local$, server$]).pipe( From 2476312b74ba4e2bb0dc035aae6eff42d913acf8 Mon Sep 17 00:00:00 2001 From: Marcus Schmidt <42213508+marschmidt89@users.noreply.github.com> Date: Wed, 16 Aug 2023 13:17:49 +0200 Subject: [PATCH 17/46] refactor: rename checkout field 'order reference id' (#1480) --------- Co-authored-by: INTERSHOPNET\AMenzel --- src/assets/i18n/de_DE.json | 8 ++++---- src/assets/i18n/en_US.json | 8 ++++---- src/assets/i18n/fr_FR.json | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/assets/i18n/de_DE.json b/src/assets/i18n/de_DE.json index d6c36b37d1..79b6fffa95 100644 --- a/src/assets/i18n/de_DE.json +++ b/src/assets/i18n/de_DE.json @@ -715,10 +715,10 @@ "checkout.order.shipping.label": "Versand", "checkout.order.total_cost.label": "Gesamtkosten", "checkout.orderReferenceId.apply.button.label": "Übernehmen", - "checkout.orderReferenceId.label": "Bestellungs-ID", + "checkout.orderReferenceId.label": "Kundenbestellnummer", "checkout.orderReferenceId.note": "Sie können eine ID für Ihre eigene Buchhaltung eingeben. Diese wird auf der Rechnung und dem Packzettel erscheinen.", - "checkout.orderReferenceId.success.message": "Ihre Bestellungs-ID wurde übernommen.", - "checkout.orderReferenceId.title": "Bestellungs-ID eingeben", + "checkout.orderReferenceId.success.message": "Ihre Kundenbestellnummer wurde übernommen.", + "checkout.orderReferenceId.title": "Kundenbestellnummer eingeben", "checkout.order_details.heading": "Bestellübersicht", "checkout.order_review.heading.text": "Prüfen Sie die Details Ihrer Bestellung und nehmen Sie, falls notwendig, noch Änderungen vor. Klicken Sie auf \"Bestellung absenden\", um den Bestellvorgang abzuschließen.", "checkout.order_review.heading.title": "Ihre Bestelldaten prüfen", @@ -802,7 +802,7 @@ "checkout.widget.buyer.TaxationID": "Steuernummer:", "checkout.widget.buyer.costcenter": "Kostenstelle", "checkout.widget.buyer.heading": "Einkäufer", - "checkout.widget.buyer.orderReferenceId": "Bestellungs-ID", + "checkout.widget.buyer.orderReferenceId": "Kundenbestellnummer", "checkout.widget.message_to_merchant": "Nachricht an den Händler", "checkout.widget.payment_method.heading": "Zahlungsart", "checkout.widget.promotion.discount": "Rabatt", diff --git a/src/assets/i18n/en_US.json b/src/assets/i18n/en_US.json index 5fa095e2cb..5959a20143 100644 --- a/src/assets/i18n/en_US.json +++ b/src/assets/i18n/en_US.json @@ -715,10 +715,10 @@ "checkout.order.shipping.label": "Shipping", "checkout.order.total_cost.label": "Total Cost", "checkout.orderReferenceId.apply.button.label": "Apply", - "checkout.orderReferenceId.label": "Order Reference ID", + "checkout.orderReferenceId.label": "Customer Order ID", "checkout.orderReferenceId.note": "You can enter an ID for your own bookkeeping. It will appear on your invoice and packing slip.", - "checkout.orderReferenceId.success.message": "Your order reference ID has been applied.", - "checkout.orderReferenceId.title": "Enter an order reference ID", + "checkout.orderReferenceId.success.message": "Your customer order ID has been applied.", + "checkout.orderReferenceId.title": "Enter a customer order ID", "checkout.order_details.heading": "Order Summary", "checkout.order_review.heading.text": "Review the details of your order below and make any changes if needed. Click \"Submit Order\" to complete your purchase.", "checkout.order_review.heading.title": "Review Your Order Information", @@ -802,7 +802,7 @@ "checkout.widget.buyer.TaxationID": "Taxation ID:", "checkout.widget.buyer.costcenter": "Cost Center", "checkout.widget.buyer.heading": "Buyer", - "checkout.widget.buyer.orderReferenceId": "Order Reference ID", + "checkout.widget.buyer.orderReferenceId": "Customer Order ID", "checkout.widget.message_to_merchant": "Message to Merchant", "checkout.widget.payment_method.heading": "Payment Method", "checkout.widget.promotion.discount": "Discount", diff --git a/src/assets/i18n/fr_FR.json b/src/assets/i18n/fr_FR.json index d34ee7b8f0..3a36b70a1d 100644 --- a/src/assets/i18n/fr_FR.json +++ b/src/assets/i18n/fr_FR.json @@ -715,10 +715,10 @@ "checkout.order.shipping.label": "Livraison", "checkout.order.total_cost.label": "Coût total", "checkout.orderReferenceId.apply.button.label": "Appliquer", - "checkout.orderReferenceId.label": "ID de référence de la commande", + "checkout.orderReferenceId.label": "ID de commande client", "checkout.orderReferenceId.note": "Vous pouvez entrer un ID pour votre propre comptabilité. Il apparaîtra sur votre facture et sur votre bordereau d’expédition.", - "checkout.orderReferenceId.success.message": "L’ID de référence de votre commande a été appliqué.", - "checkout.orderReferenceId.title": "Entrer un ID de référence de commande", + "checkout.orderReferenceId.success.message": "L’ID de votre commande client a été appliqué.", + "checkout.orderReferenceId.title": "Entrer un ID de commande client", "checkout.order_details.heading": "Récapitulatif de la commande", "checkout.order_review.heading.text": "Vérifiez les détails de votre commande ci-dessous et faites des modifications si nécessaire. Cliquez « Soumettre la commande » pour effectuer l’achat.", "checkout.order_review.heading.title": "Vérifier vos informations de commande", @@ -802,7 +802,7 @@ "checkout.widget.buyer.TaxationID": "Numéro d’identification fiscale:", "checkout.widget.buyer.costcenter": "Centre de coûts", "checkout.widget.buyer.heading": "Acheteur", - "checkout.widget.buyer.orderReferenceId": "ID de référence de la commande", + "checkout.widget.buyer.orderReferenceId": "ID de commande client", "checkout.widget.message_to_merchant": "Message au marchand", "checkout.widget.payment_method.heading": "Mode de paiement", "checkout.widget.promotion.discount": "Réduction", From 9ce4cf6ee8c68a252d363c816c406b351c6d56de Mon Sep 17 00:00:00 2001 From: Marcel Eisentraut Date: Tue, 22 Aug 2023 08:24:53 +0200 Subject: [PATCH 18/46] fix: remove api-token cookie from root '/' path (#1487) * added 'options' to cookies remove method to be able to provide 'path' --- src/app/core/utils/api-token/api-token.service.ts | 2 +- src/app/core/utils/cookies/cookies.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/utils/api-token/api-token.service.ts b/src/app/core/utils/api-token/api-token.service.ts index 5e6abcdc35..4813374fc4 100644 --- a/src/app/core/utils/api-token/api-token.service.ts +++ b/src/app/core/utils/api-token/api-token.service.ts @@ -96,7 +96,7 @@ export class ApiTokenService { path: '/', }); } else { - cookiesService.remove('apiToken'); + cookiesService.remove('apiToken', { path: '/' }); } }); diff --git a/src/app/core/utils/cookies/cookies.service.ts b/src/app/core/utils/cookies/cookies.service.ts index 628cb922b7..cac0910fd8 100644 --- a/src/app/core/utils/cookies/cookies.service.ts +++ b/src/app/core/utils/cookies/cookies.service.ts @@ -34,9 +34,9 @@ export class CookiesService { return !SSR ? (this.cookiesReader()[key] as string) : undefined; } - remove(key: string) { + remove(key: string, options?: CookiesOptions) { if (!SSR) { - this.cookiesWriter()(key, undefined); + this.cookiesWriter()(key, undefined, options); } } From 58702f1f8725f4d1567f9a6e1bc9a4b554b7d1f5 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Tue, 22 Aug 2023 08:28:19 +0200 Subject: [PATCH 19/46] chore: no longer ignore compiled health check script (#1486) --- .dockerignore | 1 - dist/.gitignore | 1 - 2 files changed, 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index cff0915457..72f93afb1f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -34,7 +34,6 @@ /dist/**/* !/dist/.gitignore !/dist/entrypoint.sh -!/dist/healthcheck.js !/dist/robots.txt # from docs/.gitignore diff --git a/dist/.gitignore b/dist/.gitignore index eafd498fa1..7355e14ac4 100644 --- a/dist/.gitignore +++ b/dist/.gitignore @@ -1,5 +1,4 @@ /**/* !.gitignore !/entrypoint.sh -!/healthcheck.js !/robots.txt From 1c0bf6e1734a37a18580f0f58a6834ea1bfd967c Mon Sep 17 00:00:00 2001 From: Marcel Eisentraut Date: Wed, 23 Aug 2023 08:34:08 +0200 Subject: [PATCH 20/46] fix: remove token informations from local storage after succesful logout (#1488) * remove after succesful logout the related token information from the localStorage * adapt e2e tests --- .../specs/account/login-user.b2c.e2e-spec.ts | 21 +++++++++++++++++++ .../icm.identity-provider.spec.ts | 2 ++ .../icm.identity-provider.ts | 8 +++++-- src/app/core/services/token/token.service.ts | 4 ++++ .../punchout-identity-provider.spec.ts | 2 ++ .../punchout-identity-provider.ts | 5 ++++- 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/e2e/cypress/e2e/specs/account/login-user.b2c.e2e-spec.ts b/e2e/cypress/e2e/specs/account/login-user.b2c.e2e-spec.ts index b91d4d2ddf..e159f79e30 100644 --- a/e2e/cypress/e2e/specs/account/login-user.b2c.e2e-spec.ts +++ b/e2e/cypress/e2e/specs/account/login-user.b2c.e2e-spec.ts @@ -32,12 +32,33 @@ describe('Returning User', () => { }); }); + it('should have saved apiToken as cookie and within localStorage', () => { + cy.getCookie('apiToken').then(cookie => { + expect(cookie).to.not.be.empty; + cy.wrap(JSON.parse(decodeURIComponent(cookie.value))).should('have.property', 'type', 'user'); + }); + + cy.getAllLocalStorage().then( + localStorage => expect(localStorage[Cypress.config('baseUrl')].access_token).to.not.be.empty + ); + }); + it('should logout and be redirected to home page', () => { at(MyAccountPage, page => { page.header.logout(); }); at(HomePage); }); + + it('should have removed apiToken cookie and infos from localStorage', () => { + cy.getCookie('apiToken').then(cookie => { + expect(cookie).to.be.null; + }); + + cy.getAllLocalStorage().then( + localStorage => expect(localStorage[Cypress.config('baseUrl')].access_token).to.be.undefined + ); + }); }); describe('with wrong password', () => { diff --git a/src/app/core/identity-provider/icm.identity-provider.spec.ts b/src/app/core/identity-provider/icm.identity-provider.spec.ts index e558625b67..0d95596535 100644 --- a/src/app/core/identity-provider/icm.identity-provider.spec.ts +++ b/src/app/core/identity-provider/icm.identity-provider.spec.ts @@ -5,6 +5,7 @@ import { Observable, Subject, of } from 'rxjs'; import { anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { AccountFacade } from 'ish-core/facades/account.facade'; +import { TokenService } from 'ish-core/services/token/token.service'; import { selectQueryParam } from 'ish-core/store/core/router'; import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service'; @@ -23,6 +24,7 @@ describe('Icm Identity Provider', () => { providers: [ { provide: AccountFacade, useFactory: () => instance(accountFacade) }, { provide: ApiTokenService, useFactory: () => instance(apiTokenService) }, + { provide: TokenService, useFactory: () => instance(mock(TokenService)) }, provideMockStore(), ], }).compileComponents(); diff --git a/src/app/core/identity-provider/icm.identity-provider.ts b/src/app/core/identity-provider/icm.identity-provider.ts index 41c7586634..49317cf10d 100644 --- a/src/app/core/identity-provider/icm.identity-provider.ts +++ b/src/app/core/identity-provider/icm.identity-provider.ts @@ -3,9 +3,10 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { Store, select } from '@ngrx/store'; import { Observable, noop } from 'rxjs'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { AccountFacade } from 'ish-core/facades/account.facade'; +import { TokenService } from 'ish-core/services/token/token.service'; import { selectQueryParam } from 'ish-core/store/core/router'; import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service'; @@ -17,7 +18,8 @@ export class ICMIdentityProvider implements IdentityProvider { private router: Router, private store: Store, private apiTokenService: ApiTokenService, - private accountFacade: AccountFacade + private accountFacade: AccountFacade, + private tokenService: TokenService ) {} getCapabilities() { @@ -46,11 +48,13 @@ export class ICMIdentityProvider implements IdentityProvider { } triggerLogout(): TriggerReturnType { + // revoke current token this.accountFacade.logoutUser(); return this.accountFacade.isLoggedIn$.pipe( // wait until the user is logged out before you go to homepage to prevent unnecessary REST calls filter(loggedIn => !loggedIn), take(1), + tap(() => this.tokenService.logOut()), // remove token from storage when user is logged out switchMap(() => this.store.pipe( select(selectQueryParam('returnUrl')), diff --git a/src/app/core/services/token/token.service.ts b/src/app/core/services/token/token.service.ts index 9d03e1968f..c3296334b2 100644 --- a/src/app/core/services/token/token.service.ts +++ b/src/app/core/services/token/token.service.ts @@ -67,6 +67,10 @@ export class TokenService { ); } + logOut() { + this.oAuthService.logOut(true); + } + /** * Refresh existing tokens, when token is about to expire * diff --git a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts index 27b0f50212..f767cf911c 100644 --- a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts +++ b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.spec.ts @@ -8,6 +8,7 @@ import { anyString, anything, instance, mock, resetCalls, verify, when } from 't import { AccountFacade } from 'ish-core/facades/account.facade'; import { AppFacade } from 'ish-core/facades/app.facade'; import { CheckoutFacade } from 'ish-core/facades/checkout.facade'; +import { TokenService } from 'ish-core/services/token/token.service'; import { selectQueryParam } from 'ish-core/store/core/router'; import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service'; import { CookiesService } from 'ish-core/utils/cookies/cookies.service'; @@ -46,6 +47,7 @@ describe('Punchout Identity Provider', () => { { provide: CheckoutFacade, useFactory: () => instance(checkoutFacade) }, { provide: CookiesService, useFactory: () => instance(cookiesService) }, { provide: PunchoutService, useFactory: () => instance(punchoutService) }, + { provide: TokenService, useFactory: () => instance(mock(TokenService)) }, provideMockStore(), ], }).compileComponents(); diff --git a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts index bbaf35fcb2..d2e7a8d9f9 100644 --- a/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts +++ b/src/app/extensions/punchout/identity-provider/punchout-identity-provider.ts @@ -9,6 +9,7 @@ import { AccountFacade } from 'ish-core/facades/account.facade'; import { AppFacade } from 'ish-core/facades/app.facade'; import { CheckoutFacade } from 'ish-core/facades/checkout.facade'; import { IdentityProvider, TriggerReturnType } from 'ish-core/identity-provider/identity-provider.interface'; +import { TokenService } from 'ish-core/services/token/token.service'; import { selectQueryParam } from 'ish-core/store/core/router'; import { ApiTokenService } from 'ish-core/utils/api-token/api-token.service'; import { CookiesService } from 'ish-core/utils/cookies/cookies.service'; @@ -26,7 +27,8 @@ export class PunchoutIdentityProvider implements IdentityProvider { private accountFacade: AccountFacade, private punchoutService: PunchoutService, private cookiesService: CookiesService, - private checkoutFacade: CheckoutFacade + private checkoutFacade: CheckoutFacade, + private tokenService: TokenService ) {} getCapabilities() { @@ -132,6 +134,7 @@ export class PunchoutIdentityProvider implements IdentityProvider { // wait until the user is logged out before you go to homepage to prevent unnecessary REST calls filter(loggedIn => !loggedIn), take(1), + tap(() => this.tokenService.logOut()), // remove token from storage when user is logged out switchMap(() => this.store.pipe( select(selectQueryParam('returnUrl')), From e54f611bb38ab9e6b339fb349c5606a2545d5814 Mon Sep 17 00:00:00 2001 From: Marcel Eisentraut Date: Fri, 25 Aug 2023 09:21:40 +0200 Subject: [PATCH 21/46] perf: use better suited operators for REST requests (#1491) * replace some concatMap operator with mergeMap * replace some concatMap operators with switchMap within effects --- src/app/core/store/content/page-tree/page-tree.effects.ts | 4 ++-- src/app/core/store/content/parameters/parameters.effects.ts | 4 ++-- .../core/store/content/viewcontexts/viewcontexts.effects.ts | 4 ++-- .../core/store/core/server-config/server-config.effects.ts | 4 ++-- src/app/core/store/customer/basket/basket-items.effects.ts | 2 +- src/app/core/store/customer/basket/basket-payment.effects.ts | 2 +- src/app/core/store/customer/basket/basket.effects.ts | 2 +- src/app/core/store/customer/orders/orders.effects.ts | 2 +- src/app/core/store/general/countries/countries.effects.ts | 4 ++-- src/app/core/store/general/regions/regions.effects.ts | 4 ++-- src/app/core/store/shopping/products/products.effects.ts | 5 ++--- .../extensions/contact-us/store/contact/contact.effects.ts | 4 ++-- .../punchout/store/punchout-types/punchout-types.effects.ts | 4 ++-- 13 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/app/core/store/content/page-tree/page-tree.effects.ts b/src/app/core/store/content/page-tree/page-tree.effects.ts index 5549acb2db..7236a77b6b 100644 --- a/src/app/core/store/content/page-tree/page-tree.effects.ts +++ b/src/app/core/store/content/page-tree/page-tree.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { concatMap, map } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { CMSService } from 'ish-core/services/cms/cms.service'; import { mapErrorToAction, mapToPayload } from 'ish-core/utils/operators'; @@ -15,7 +15,7 @@ export class PageTreeEffects { this.actions$.pipe( ofType(loadContentPageTree), mapToPayload(), - concatMap(({ rootId, depth }) => + mergeMap(({ rootId, depth }) => this.cmsService.getContentPageTree(rootId, depth).pipe( map(pagetree => loadContentPageTreeSuccess({ pagetree })), mapErrorToAction(loadContentPageTreeFail) diff --git a/src/app/core/store/content/parameters/parameters.effects.ts b/src/app/core/store/content/parameters/parameters.effects.ts index 71ce566498..d7ffd386b7 100644 --- a/src/app/core/store/content/parameters/parameters.effects.ts +++ b/src/app/core/store/content/parameters/parameters.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { concatMap, mergeMap } from 'rxjs/operators'; +import { mergeMap } from 'rxjs/operators'; import { Product } from 'ish-core/models/product/product.model'; import { ProductsService } from 'ish-core/services/products/products.service'; @@ -21,7 +21,7 @@ export class ParametersEffects { this.actions$.pipe( ofType(loadParametersProductListFilter), mapToPayload(), - concatMap(({ id, searchParameter, amount }) => + mergeMap(({ id, searchParameter, amount }) => this.productsService.getFilteredProducts(searchParameter, amount).pipe( mergeMap(({ products }) => [ ...products.map((product: Product) => loadProductSuccess({ product })), diff --git a/src/app/core/store/content/viewcontexts/viewcontexts.effects.ts b/src/app/core/store/content/viewcontexts/viewcontexts.effects.ts index 83ab12c1ef..168739e4bc 100644 --- a/src/app/core/store/content/viewcontexts/viewcontexts.effects.ts +++ b/src/app/core/store/content/viewcontexts/viewcontexts.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { concatMap, map } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { CMSService } from 'ish-core/services/cms/cms.service'; import { mapErrorToAction, mapToPayload } from 'ish-core/utils/operators'; @@ -19,7 +19,7 @@ export class ViewcontextsEffects { this.actions$.pipe( ofType(loadViewContextEntrypoint), mapToPayload(), - concatMap(({ viewContextId, callParameters }) => + mergeMap(({ viewContextId, callParameters }) => this.cmsService.getViewContextContent(viewContextId, callParameters).pipe( map(({ entrypoint, pagelets }) => loadViewContextEntrypointSuccess({ entrypoint, pagelets, viewContextId, callParameters }) diff --git a/src/app/core/store/core/server-config/server-config.effects.ts b/src/app/core/store/core/server-config/server-config.effects.ts index 393f62f252..b92098d743 100644 --- a/src/app/core/store/core/server-config/server-config.effects.ts +++ b/src/app/core/store/core/server-config/server-config.effects.ts @@ -48,7 +48,7 @@ export class ServerConfigEffects { loadServerConfig$ = createEffect(() => this.actions$.pipe( ofType(loadServerConfig), - concatMap(() => + switchMap(() => this.configService.getServerConfiguration().pipe( map(config => loadServerConfigSuccess({ config })), mapErrorToAction(loadServerConfigFail) @@ -64,7 +64,7 @@ export class ServerConfigEffects { switchMap(() => this.store.pipe(select(isExtraConfigurationLoaded))), whenFalsy(), delayUntil(this.actions$.pipe(ofType(personalizationStatusDetermined))), - concatMap(() => + switchMap(() => this.configService.getExtraConfiguration().pipe( map(extra => loadExtraConfigSuccess({ extra })), mapErrorToAction(loadExtraConfigFail) diff --git a/src/app/core/store/customer/basket/basket-items.effects.ts b/src/app/core/store/customer/basket/basket-items.effects.ts index 4b24d8304f..4abc047b69 100644 --- a/src/app/core/store/customer/basket/basket-items.effects.ts +++ b/src/app/core/store/customer/basket/basket-items.effects.ts @@ -105,7 +105,7 @@ export class BasketItemsEffects { this.actions$.pipe( ofType(addItemsToBasket), mapToPayload(), - concatMap(payload => [...payload.items.map(item => loadProduct({ sku: item.sku }))]) + mergeMap(payload => [...payload.items.map(item => loadProduct({ sku: item.sku }))]) ) ); diff --git a/src/app/core/store/customer/basket/basket-payment.effects.ts b/src/app/core/store/customer/basket/basket-payment.effects.ts index a875a62b45..630e53f941 100644 --- a/src/app/core/store/customer/basket/basket-payment.effects.ts +++ b/src/app/core/store/customer/basket/basket-payment.effects.ts @@ -42,7 +42,7 @@ export class BasketPaymentEffects { loadBasketEligiblePaymentMethods$ = createEffect(() => this.actions$.pipe( ofType(loadBasketEligiblePaymentMethods), - concatMap(() => + switchMap(() => this.paymentService.getBasketEligiblePaymentMethods().pipe( map(result => loadBasketEligiblePaymentMethodsSuccess({ paymentMethods: result })), mapErrorToAction(loadBasketEligiblePaymentMethodsFail) diff --git a/src/app/core/store/customer/basket/basket.effects.ts b/src/app/core/store/customer/basket/basket.effects.ts index e7e2ee9cb6..b6c5a7b455 100644 --- a/src/app/core/store/customer/basket/basket.effects.ts +++ b/src/app/core/store/customer/basket/basket.effects.ts @@ -116,7 +116,7 @@ export class BasketEffects { this.actions$.pipe( ofType(loadBasketByAPIToken), mapToPayloadProperty('apiToken'), - concatMap(apiToken => + switchMap(apiToken => this.basketService.getBasketByToken(apiToken).pipe( map(basket => loadBasketSuccess({ basket })), mapErrorToAction(loadBasketByAPITokenFail) diff --git a/src/app/core/store/customer/orders/orders.effects.ts b/src/app/core/store/customer/orders/orders.effects.ts index 6a6bb1dcb9..4ae246cd20 100644 --- a/src/app/core/store/customer/orders/orders.effects.ts +++ b/src/app/core/store/customer/orders/orders.effects.ts @@ -122,7 +122,7 @@ export class OrdersEffects { loadOrders$ = createEffect(() => this.actions$.pipe( ofType(loadOrders), - concatMap(() => + switchMap(() => this.orderService.getOrders().pipe( map(orders => loadOrdersSuccess({ orders })), mapErrorToAction(loadOrdersFail) diff --git a/src/app/core/store/general/countries/countries.effects.ts b/src/app/core/store/general/countries/countries.effects.ts index 0d30ef0750..f19d0bfe1f 100644 --- a/src/app/core/store/general/countries/countries.effects.ts +++ b/src/app/core/store/general/countries/countries.effects.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; -import { concatMap, filter, map, withLatestFrom } from 'rxjs/operators'; +import { filter, map, switchMap, withLatestFrom } from 'rxjs/operators'; import { CountryService } from 'ish-core/services/country/country.service'; import { mapErrorToAction } from 'ish-core/utils/operators'; @@ -18,7 +18,7 @@ export class CountriesEffects { ofType(loadCountries), withLatestFrom(this.store.pipe(select(getAllCountries))), filter(([, countries]) => !countries.length), - concatMap(() => + switchMap(() => this.countryService.getCountries().pipe( map(countries => loadCountriesSuccess({ countries })), mapErrorToAction(loadCountriesFail) diff --git a/src/app/core/store/general/regions/regions.effects.ts b/src/app/core/store/general/regions/regions.effects.ts index f4dd8cf9ca..a3b0ed5a9c 100644 --- a/src/app/core/store/general/regions/regions.effects.ts +++ b/src/app/core/store/general/regions/regions.effects.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; -import { concatMap, filter, map, withLatestFrom } from 'rxjs/operators'; +import { filter, map, mergeMap, withLatestFrom } from 'rxjs/operators'; import { CountryService } from 'ish-core/services/country/country.service'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; @@ -19,7 +19,7 @@ export class RegionsEffects { mapToPayloadProperty('countryCode'), withLatestFrom(this.store.pipe(select(getAllRegions))), filter(([countryCode, allRegions]) => !allRegions.some(r => r.countryCode === countryCode)), - concatMap(([countryCode]) => + mergeMap(([countryCode]) => this.countryService.getRegionsByCountry(countryCode).pipe( map(regions => loadRegionsSuccess({ regions })), mapErrorToAction(loadRegionsFail) diff --git a/src/app/core/store/shopping/products/products.effects.ts b/src/app/core/store/shopping/products/products.effects.ts index ebbad2d60d..f8e91ea0bb 100644 --- a/src/app/core/store/shopping/products/products.effects.ts +++ b/src/app/core/store/shopping/products/products.effects.ts @@ -117,7 +117,7 @@ export class ProductsEffects { map(payload => ({ ...payload, page: payload.page ? payload.page : 1 })), withLatestFrom(this.store.pipe(select(getProductListingItemsPerPage('category')))), map(([payload, pageSize]) => ({ ...payload, amount: pageSize, offset: (payload.page - 1) * pageSize })), - concatMap(({ categoryId, amount, sorting, offset, page }) => + mergeMap(({ categoryId, amount, sorting, offset, page }) => this.productsService.getCategoryProducts(categoryId, amount, sorting, offset).pipe( concatMap(({ total, products, sortableAttributes }) => [ ...products.map(product => loadProductSuccess({ product })), @@ -152,8 +152,7 @@ export class ProductsEffects { map(payload => ({ ...payload, page: payload.page ? payload.page : 1 })), withLatestFrom(this.store.pipe(select(getProductListingItemsPerPage('master')))), map(([payload, pageSize]) => ({ ...payload, amount: pageSize, offset: (payload.page - 1) * pageSize })), - - concatMap(({ masterSKU, amount, sorting, offset, page }) => + mergeMap(({ masterSKU, amount, sorting, offset, page }) => this.productsService.getProductsForMaster(masterSKU, amount, sorting, offset).pipe( concatMap(({ total, products, sortableAttributes }) => [ ...products.map(product => loadProductSuccess({ product })), diff --git a/src/app/extensions/contact-us/store/contact/contact.effects.ts b/src/app/extensions/contact-us/store/contact/contact.effects.ts index b1c60a11cc..6a20a787b6 100644 --- a/src/app/extensions/contact-us/store/contact/contact.effects.ts +++ b/src/app/extensions/contact-us/store/contact/contact.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { concatMap, map } from 'rxjs/operators'; +import { concatMap, map, switchMap } from 'rxjs/operators'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; @@ -25,7 +25,7 @@ export class ContactEffects { loadSubjects$ = createEffect(() => this.actions$.pipe( ofType(loadContact), - concatMap(() => + switchMap(() => this.contactService.getContactSubjects().pipe( map(subjects => loadContactSuccess({ subjects })), mapErrorToAction(loadContactFail) diff --git a/src/app/extensions/punchout/store/punchout-types/punchout-types.effects.ts b/src/app/extensions/punchout/store/punchout-types/punchout-types.effects.ts index 58d1e6c01f..65e27f0ef6 100644 --- a/src/app/extensions/punchout/store/punchout-types/punchout-types.effects.ts +++ b/src/app/extensions/punchout/store/punchout-types/punchout-types.effects.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigatedAction } from '@ngrx/router-store'; -import { concatMap, filter, map } from 'rxjs/operators'; +import { filter, map, switchMap } from 'rxjs/operators'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; @@ -25,7 +25,7 @@ export class PunchoutTypesEffects { loadPunchoutTypes$ = createEffect(() => this.actions$.pipe( ofType(loadPunchoutTypes), - concatMap(() => + switchMap(() => this.punchoutService.getPunchoutTypes().pipe( map(types => loadPunchoutTypesSuccess({ types })), mapErrorToAction(loadPunchoutTypesFail) From cfe40ffdd5077e7c6b828703af14d87b248e87b6 Mon Sep 17 00:00:00 2001 From: Lucas Hengelhaupt Date: Thu, 25 May 2023 16:46:46 +0200 Subject: [PATCH 22/46] feat: accessibility improvements --- .../requisitions-list.component.html | 4 ++- .../quote-edit/quote-edit.component.html | 13 +++++----- .../quote-view/quote-view.component.html | 5 ++++ .../cms-image-enhanced.component.html | 2 +- .../cms-image-enhanced.component.spec.ts | 2 ++ .../cms-image/cms-image.component.html | 6 ++++- .../cms-image/cms-image.component.spec.ts | 2 ++ ...sket-invoice-address-widget.component.html | 4 +-- ...basket-invoice-address-widget.component.ts | 2 +- ...ket-shipping-address-widget.component.html | 4 +-- ...asket-shipping-address-widget.component.ts | 2 +- .../breadcrumb/breadcrumb.component.html | 10 ++++--- .../in-place-edit.component.html | 13 ++++------ .../in-place-edit.component.scss | 5 ++-- .../in-place-edit.component.spec.ts | 26 ++++++++++++------- .../in-place-edit/in-place-edit.component.ts | 24 ++++++++++------- .../common/info-box/info-box.component.html | 2 +- .../filter-dropdown.component.html | 4 +-- .../filter-dropdown.component.scss | 4 ++- .../filter-dropdown.component.spec.ts | 5 ++-- .../product-list-toolbar.component.html | 24 ++++++++--------- .../header-checkout.component.html | 2 ++ .../header-default.component.spec.ts.snap | 6 +++++ .../header-default.component.html | 2 ++ .../header-simple.component.html | 2 ++ .../language-switch.component.html | 10 +++++-- src/assets/i18n/de_DE.json | 16 +++++++----- src/assets/i18n/en_US.json | 16 +++++++----- src/assets/i18n/fr_FR.json | 16 +++++++----- .../components/header/language-switch.scss | 13 +++++++--- src/styles/pages/category/filter-row.scss | 8 +++++- 31 files changed, 163 insertions(+), 91 deletions(-) diff --git a/projects/requisition-management/src/app/components/requisitions-list/requisitions-list.component.html b/projects/requisition-management/src/app/components/requisitions-list/requisitions-list.component.html index 6b1e3edf69..98c5c1de68 100644 --- a/projects/requisition-management/src/app/components/requisitions-list/requisitions-list.component.html +++ b/projects/requisition-management/src/app/components/requisitions-list/requisitions-list.component.html @@ -15,7 +15,9 @@ *cdkCellDef="let requisition" [attr.data-label]="'account.approvallist.table.id_of_order' | translate" > - {{ requisition.requisitionNo }} + + {{ requisition.requisitionNo }} + diff --git a/src/app/extensions/quoting/shared/quote-edit/quote-edit.component.html b/src/app/extensions/quoting/shared/quote-edit/quote-edit.component.html index e86bb8d6c4..a54b7e1119 100644 --- a/src/app/extensions/quoting/shared/quote-edit/quote-edit.component.html +++ b/src/app/extensions/quoting/shared/quote-edit/quote-edit.component.html @@ -18,9 +18,9 @@
- +

@@ -31,10 +31,11 @@

+
- +

diff --git a/src/app/extensions/quoting/shared/quote-view/quote-view.component.html b/src/app/extensions/quoting/shared/quote-view/quote-view.component.html index e6f65b7011..17657dc221 100644 --- a/src/app/extensions/quoting/shared/quote-view/quote-view.component.html +++ b/src/app/extensions/quoting/shared/quote-view/quote-view.component.html @@ -36,6 +36,7 @@ > +

{{ 'quote.edit.unsubmitted.quote_no.label' | translate }} @@ -46,6 +47,8 @@

+ +
{{ 'quote.edit.unsubmitted.status.label' | translate }}
@@ -73,6 +76,7 @@
+
{{ 'quote.edit.unsubmitted.comment.label' | translate }} @@ -82,6 +86,7 @@
+
{{ 'quote.edit.unsubmitted.seller_comment.label' | translate }} diff --git a/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.html b/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.html index 8d479dda5a..c858deee45 100644 --- a/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.html +++ b/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.html @@ -32,7 +32,7 @@ diff --git a/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.spec.ts b/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.spec.ts index a4293ec078..ff12ca5922 100644 --- a/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.spec.ts +++ b/src/app/shared/cms/components/cms-image-enhanced/cms-image-enhanced.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; import { createContentPageletView } from 'ish-core/models/content-view/content-view.model'; @@ -11,6 +12,7 @@ describe('Cms Image Enhanced Component', () => { beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], declarations: [CMSImageEnhancedComponent], }).compileComponents(); }); diff --git a/src/app/shared/cms/components/cms-image/cms-image.component.html b/src/app/shared/cms/components/cms-image/cms-image.component.html index 3645b6262b..5b0c4a8d12 100644 --- a/src/app/shared/cms/components/cms-image/cms-image.component.html +++ b/src/app/shared/cms/components/cms-image/cms-image.component.html @@ -13,6 +13,10 @@
- +
diff --git a/src/app/shared/cms/components/cms-image/cms-image.component.spec.ts b/src/app/shared/cms/components/cms-image/cms-image.component.spec.ts index 0c66852bdc..643c8f7df0 100644 --- a/src/app/shared/cms/components/cms-image/cms-image.component.spec.ts +++ b/src/app/shared/cms/components/cms-image/cms-image.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; import { createContentPageletView } from 'ish-core/models/content-view/content-view.model'; @@ -11,6 +12,7 @@ describe('Cms Image Component', () => { beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], declarations: [CMSImageComponent], }).compileComponents(); }); diff --git a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.html b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.html index 06d2e0df10..dc7d5020d3 100644 --- a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.html +++ b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.html @@ -6,7 +6,7 @@

{{ 'checkout.address.billing.label' | translate }}

*ngIf="collapseChange | async" [routerLink]="[]" class="btn-tool float-right" - title="{{ 'checkout.address.update.button.label' | translate }}" + title="{{ 'checkout.address.update.button.invoice.label' | translate }}" (click)="showAddressForm(address)" data-testing-id="edit-invoice-address-link" > @@ -37,7 +37,7 @@

{{ 'checkout.address.billing.label' | translate }}

[attr.aria-expanded]="(collapseChange | async) === false" aria-controls="invoice-address-panel" > - {{ 'checkout.create_address.link' | translate }} + {{ 'checkout.create_invoice_address.link' | translate }}
diff --git a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts index bd9ef4e87c..121cc20963 100644 --- a/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts +++ b/src/app/shared/components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component.ts @@ -59,7 +59,7 @@ export class BasketInvoiceAddressWidgetComponent implements OnInit, OnDestroy { .pipe( map(address => address - ? 'checkout.addresses.select_a_different_address.default' + ? 'checkout.addresses.select_a_different_invoice_address.default' : 'checkout.addresses.select_invoice_address.button' ), takeUntil(this.destroy$) diff --git a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html index 50fd902e73..59784b6466 100644 --- a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html +++ b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.html @@ -8,7 +8,7 @@

{{ 'checkout.address.shipping.label' | translate }}

*ngIf="collapseChange | async" [routerLink]="[]" class="btn-tool" - title="{{ 'checkout.address.update.button.label' | translate }}" + title="{{ 'checkout.address.update.button.shipping.label' | translate }}" (click)="showAddressForm(address)" data-testing-id="edit-shipping-address-link" > @@ -72,7 +72,7 @@

{{ 'checkout.address.shipping.label' | translate }}

[attr.aria-expanded]="(collapseChange | async) === false" aria-controls="shipping-address-panel" > - {{ 'checkout.create_address.link' | translate }} + {{ 'checkout.create_shipping_address.link' | translate }}
diff --git a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts index 583b9d57d1..26560d3973 100644 --- a/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts +++ b/src/app/shared/components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component.ts @@ -69,7 +69,7 @@ export class BasketShippingAddressWidgetComponent implements OnInit, OnDestroy { .pipe( map(address => address - ? 'checkout.addresses.select_a_different_address.default' + ? 'checkout.addresses.select_a_different_shipping_address.default' : 'checkout.addresses.select_shipping_address.button' ), takeUntil(this.destroy$) diff --git a/src/app/shared/components/common/breadcrumb/breadcrumb.component.html b/src/app/shared/components/common/breadcrumb/breadcrumb.component.html index e35a4ccf72..1c465c9152 100644 --- a/src/app/shared/components/common/breadcrumb/breadcrumb.component.html +++ b/src/app/shared/components/common/breadcrumb/breadcrumb.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/app/shared/components/common/in-place-edit/in-place-edit.component.html b/src/app/shared/components/common/in-place-edit/in-place-edit.component.html index c4d210e72e..10b2f581a7 100644 --- a/src/app/shared/components/common/in-place-edit/in-place-edit.component.html +++ b/src/app/shared/components/common/in-place-edit/in-place-edit.component.html @@ -1,19 +1,16 @@ -
+
- +
`); }); @@ -73,7 +75,7 @@ describe('In Place Edit Component', () => { >
{

VIEW

-
+
`); }); @@ -123,8 +127,10 @@ describe('In Place Edit Component', () => {

VIEW

-
+
`); }); @@ -145,8 +151,10 @@ describe('In Place Edit Component', () => {

VIEW

-
+
`); }); diff --git a/src/app/shared/components/common/in-place-edit/in-place-edit.component.ts b/src/app/shared/components/common/in-place-edit/in-place-edit.component.ts index 88352573ff..00b45f316e 100644 --- a/src/app/shared/components/common/in-place-edit/in-place-edit.component.ts +++ b/src/app/shared/components/common/in-place-edit/in-place-edit.component.ts @@ -34,6 +34,7 @@ export class InPlaceEditComponent implements AfterViewInit, OnDestroy { @Inject(DOCUMENT) private document: Document ) {} + // change into edit mode by clicking on the text ngAfterViewInit() { fromEvent(this.document, 'mousedown') .pipe( @@ -57,15 +58,26 @@ export class InPlaceEditComponent implements AfterViewInit, OnDestroy { }); } + // change into edit mode by clicking the pen + changeEditMode() { + if (this.mode === 'edit') { + setTimeout(() => { + this.host.nativeElement.querySelector('.form-control')?.focus(); + }, 200); + } + if (this.mode === 'view') { + this.confirm(); + this.mode = 'edit'; + } + } + confirm() { this.mode = 'view'; - this.unsetHover(); this.edited.emit(); } cancel() { this.mode = 'view'; - this.unsetHover(); this.aborted.emit(); } @@ -73,14 +85,6 @@ export class InPlaceEditComponent implements AfterViewInit, OnDestroy { return this.mode === 'view'; } - setHover() { - this.hover = true; - } - - unsetHover() { - this.hover = false; - } - ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); diff --git a/src/app/shared/components/common/info-box/info-box.component.html b/src/app/shared/components/common/info-box/info-box.component.html index f7e4220505..42eb4d49e9 100644 --- a/src/app/shared/components/common/info-box/info-box.component.html +++ b/src/app/shared/components/common/info-box/info-box.component.html @@ -3,7 +3,7 @@ *ngIf="editRouterLink" class="float-right btn-tool" [routerLink]="editRouterLink" - [title]="'checkout.address.update.label' | translate" + [title]="'checkout.address.update.label' | translate : { '0': heading | translate }" > diff --git a/src/app/shared/components/filter/filter-dropdown/filter-dropdown.component.html b/src/app/shared/components/filter/filter-dropdown/filter-dropdown.component.html index 57ec446ec8..aedb27f414 100644 --- a/src/app/shared/components/filter/filter-dropdown/filter-dropdown.component.html +++ b/src/app/shared/components/filter/filter-dropdown/filter-dropdown.component.html @@ -1,5 +1,5 @@
- {{ placeholder }} - +
{ fixture.detectChanges(); expect(element).toMatchInlineSnapshot(` diff --git a/src/app/shell/header/header-checkout/header-checkout.component.html b/src/app/shell/header/header-checkout/header-checkout.component.html index 0848810915..f8e4864f5b 100644 --- a/src/app/shell/header/header-checkout/header-checkout.component.html +++ b/src/app/shell/header/header-checkout/header-checkout.component.html @@ -11,6 +11,7 @@ routerLink="/home" class="mobile-logo" [attr.aria-label]="'common.home.link' | translate" + role="img" data-testing-id="link-home" >
@@ -20,6 +21,7 @@ routerLink="/home" class="logo" [attr.aria-label]="'common.home.link' | translate" + role="img" data-testing-id="header-home-link-desktop" >
diff --git a/src/app/shell/header/header-default/__snapshots__/header-default.component.spec.ts.snap b/src/app/shell/header/header-default/__snapshots__/header-default.component.spec.ts.snap index 1a720d4f0a..583541dd01 100644 --- a/src/app/shell/header/header-default/__snapshots__/header-default.component.spec.ts.snap +++ b/src/app/shell/header/header-default/__snapshots__/header-default.component.spec.ts.snap @@ -36,6 +36,7 @@ exports[`Header Default Component should render normal header adequately for des
diff --git a/src/app/shell/header/header-simple/header-simple.component.html b/src/app/shell/header/header-simple/header-simple.component.html index 86f762eb0b..8f0949c835 100644 --- a/src/app/shell/header/header-simple/header-simple.component.html +++ b/src/app/shell/header/header-simple/header-simple.component.html @@ -6,6 +6,7 @@ routerLink="/home" class="logo" [attr.aria-label]="'common.home.link' | translate" + role="img" data-testing-id="header-home-link-desktop" >
@@ -15,6 +16,7 @@ routerLink="/home" class="mobile-logo" [attr.aria-label]="'common.home.link' | translate" + role="img" data-testing-id="link-home" > diff --git a/src/app/shell/header/language-switch/language-switch.component.html b/src/app/shell/header/language-switch/language-switch.component.html index c3629556e1..d2c99eea73 100644 --- a/src/app/shell/header/language-switch/language-switch.component.html +++ b/src/app/shell/header/language-switch/language-switch.component.html @@ -6,13 +6,19 @@ ngbDropdown placement="{{ placement === 'up' ? 'top-right' : 'bottom-right' }}" > - +