diff --git a/feature-libs/user/profile/components/register/register-component.service.spec.ts b/feature-libs/user/profile/components/register/register-component.service.spec.ts index a8d67d3daed..2903aa798b4 100644 --- a/feature-libs/user/profile/components/register/register-component.service.spec.ts +++ b/feature-libs/user/profile/components/register/register-component.service.spec.ts @@ -10,6 +10,18 @@ import { of } from 'rxjs'; import { RegisterComponentService } from './register-component.service'; import createSpy = jasmine.createSpy; +const mockRegisterFormData: any = { + titleCode: 'Mr', + firstName: 'John', + lastName: 'Doe', + email: 'JohnDoe@thebest.john.intheworld.com', + email_lowercase: 'johndoe@thebest.john.intheworld.com', + termsandconditions: true, + password: 'strongPass$!123', + passwordconf: 'strongPass$!123', + newsletter: true, + captcha: true, +}; class MockUserRegisterFacade implements Partial { getTitles = createSpy().and.returnValue(of([])); @@ -98,4 +110,14 @@ describe('RegisterComponentService', () => { let result = service.getAdditionalConsents(); expect(result).toEqual([]); }); + it('collectDataFromRegisterForm()', () => { + const form = mockRegisterFormData; + expect(service.collectDataFromRegisterForm(form)).toEqual({ + firstName: form.firstName, + lastName: form.lastName, + uid: form.email_lowercase, + password: form.password, + titleCode: form.titleCode, + }); + }); }); diff --git a/feature-libs/user/profile/components/register/register-component.service.ts b/feature-libs/user/profile/components/register/register-component.service.ts index 307c6b1496c..6ffe8aa6964 100644 --- a/feature-libs/user/profile/components/register/register-component.service.ts +++ b/feature-libs/user/profile/components/register/register-component.service.ts @@ -83,4 +83,15 @@ export class RegisterComponentService { generateAdditionalConsentsFormControl(): UntypedFormArray | undefined { return this.fb?.array([]) ?? undefined; } + + collectDataFromRegisterForm(formData: any): UserSignUp { + const { firstName, lastName, email, password, titleCode } = formData; + return { + firstName, + lastName, + password, + titleCode, + uid: email.toLowerCase(), + }; + } } diff --git a/feature-libs/user/profile/components/register/register.component.html b/feature-libs/user/profile/components/register/register.component.html index 5a5fb60fa1b..8cf30ca2710 100644 --- a/feature-libs/user/profile/components/register/register.component.html +++ b/feature-libs/user/profile/components/register/register.component.html @@ -226,8 +226,9 @@
-
@@ -249,6 +251,7 @@
diff --git a/feature-libs/user/profile/components/register/register.component.spec.ts b/feature-libs/user/profile/components/register/register.component.spec.ts index d73af049311..20337119cb9 100644 --- a/feature-libs/user/profile/components/register/register.component.spec.ts +++ b/feature-libs/user/profile/components/register/register.component.spec.ts @@ -125,6 +125,7 @@ class MockRegisterComponentService postRegisterMessage = createSpy(); getAdditionalConsents = createSpy(); generateAdditionalConsentsFormControl = createSpy(); + collectDataFromRegisterForm = createSpy(); } class MockSiteAdapter { @@ -309,19 +310,23 @@ describe('RegisterComponent', () => { describe('collectDataFromRegisterForm()', () => { it('should return correct register data', () => { const form = mockRegisterFormData; - - expect(component.collectDataFromRegisterForm(form)).toEqual({ - firstName: form.firstName, - lastName: form.lastName, - uid: form.email_lowercase, - password: form.password, - titleCode: form.titleCode, - }); + component.collectDataFromRegisterForm(form); + expect( + registerComponentService.collectDataFromRegisterForm + ).toHaveBeenCalledWith(form); }); }); describe('register', () => { it('should register with valid form', () => { + regComponentService.collectDataFromRegisterForm = + createSpy().and.returnValue({ + firstName: mockRegisterFormData.firstName, + lastName: mockRegisterFormData.lastName, + uid: mockRegisterFormData.email_lowercase, + password: mockRegisterFormData.password, + titleCode: mockRegisterFormData.titleCode, + }); component.registerForm.patchValue(mockRegisterFormData); component.ngOnInit(); component.submitForm(); diff --git a/feature-libs/user/profile/components/register/register.component.ts b/feature-libs/user/profile/components/register/register.component.ts index 295b429b717..92e2eea19e4 100644 --- a/feature-libs/user/profile/components/register/register.component.ts +++ b/feature-libs/user/profile/components/register/register.component.ts @@ -113,7 +113,8 @@ export class RegisterComponent implements OnInit, OnDestroy { updateAdditionalConsents(event: MouseEvent, index: number) { const { checked } = event.target as HTMLInputElement; - this.registerForm.value.additionalConsents[index] = checked; + this.registerForm.value.additionalConsents[index].isConsentGranted = + checked; } constructor( @@ -215,15 +216,7 @@ export class RegisterComponent implements OnInit, OnDestroy { } collectDataFromRegisterForm(formData: any): UserSignUp { - const { firstName, lastName, email, password, titleCode } = formData; - - return { - firstName, - lastName, - uid: email.toLowerCase(), - password, - titleCode, - }; + return this.registerComponentService.collectDataFromRegisterForm(formData); } isConsentGiven(consent: AnonymousConsent | undefined): boolean { diff --git a/integration-libs/cdc/root/consent-management/cdc-consent.module.ts b/integration-libs/cdc/root/consent-management/cdc-consent.module.ts index 64a0c987432..22a2542972b 100644 --- a/integration-libs/cdc/root/consent-management/cdc-consent.module.ts +++ b/integration-libs/cdc/root/consent-management/cdc-consent.module.ts @@ -10,8 +10,12 @@ import { CdcUserPreferenceSerializer } from './converters/cdc-user-preference.se import { CommonModule } from '@angular/common'; import { CdcConsentManagementComponentService } from './services/cdc-consent-management-component.service'; import { ConsentManagementComponentService } from '@spartacus/storefront'; -import { CDC_USER_PREFERENCE_SERIALIZER } from './converters/converter'; +import { + CDC_PREFERENCE_SERIALIZER, + CDC_USER_PREFERENCE_SERIALIZER, +} from './converters/converter'; import { CdcUserConsentAdapter } from './cdc-user-consent.adapter'; +import { CdcPreferenceSerializer } from './converters'; @NgModule({ imports: [CommonModule, I18nModule], @@ -26,6 +30,11 @@ import { CdcUserConsentAdapter } from './cdc-user-consent.adapter'; useExisting: CdcUserPreferenceSerializer, multi: true, }, + { + provide: CDC_PREFERENCE_SERIALIZER, + useExisting: CdcPreferenceSerializer, + multi: true, + }, ], }) export class CdcConsentManagementModule {} diff --git a/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.spec.ts b/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.spec.ts index aff7ddd159b..1e1b82cc194 100644 --- a/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.spec.ts +++ b/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.spec.ts @@ -17,7 +17,7 @@ import createSpy = jasmine.createSpy; const consentTemplateId = 'xxxx'; const consentTemplateVersion = 0; class MockCdcUserConsentService implements Partial { - updateCdcConsent = createSpy(); + updateCdcUserPreferences = createSpy(); } class MockCdcConsentsLocalStorageService implements Partial @@ -61,16 +61,14 @@ describe('CdcUserConsentAdapter', () => { describe('giveConsent()', () => { it('should update cdc consent', () => { storage.checkIfConsentExists = createSpy().and.returnValue(true); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 0 }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 0 })); service .giveConsent('current', consentTemplateId, consentTemplateVersion) .subscribe(); - expect(cdcUserConsentService.updateCdcConsent).toHaveBeenCalledWith( - true, - ['xxxx'] - ); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).toHaveBeenCalledWith([{ id: 'xxxx', isConsentGranted: true }]); httpMock.expectOne((req) => { return ( req.method === 'POST' && @@ -82,25 +80,21 @@ describe('CdcUserConsentAdapter', () => { }); it('should not call CDC SDK', () => { storage.checkIfConsentExists = createSpy().and.returnValue(false); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 0 }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 0 })); service.giveConsent('current', 'xxxx', 0).subscribe(); - expect(cdcUserConsentService.updateCdcConsent).not.toHaveBeenCalledWith( - true, - ['xxxx'] - ); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).not.toHaveBeenCalledWith([{ id: 'xxxx', isConsentGranted: true }]); }); it('should not call Commerce API', () => { storage.checkIfConsentExists = createSpy().and.returnValue(true); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 2 }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 2 })); service.giveConsent('current', 'xxxx', 0).subscribe(); - expect(cdcUserConsentService.updateCdcConsent).toHaveBeenCalledWith( - true, - ['xxxx'] - ); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).toHaveBeenCalledWith([{ id: 'xxxx', isConsentGranted: true }]); httpMock.expectNone((req) => { return ( req.method === 'POST' && @@ -114,14 +108,12 @@ describe('CdcUserConsentAdapter', () => { describe('withdrawConsent()', () => { it('should update cdc consent', () => { storage.checkIfConsentExists = createSpy().and.returnValue(true); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 0 }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 0 })); service.withdrawConsent('current', 'code', 'xxxx').subscribe(); - expect(cdcUserConsentService.updateCdcConsent).toHaveBeenCalledWith( - false, - ['xxxx'] - ); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).toHaveBeenCalledWith([{ id: 'xxxx', isConsentGranted: false }]); httpMock.expectOne((req) => { return req.method === 'DELETE'; }); @@ -129,25 +121,21 @@ describe('CdcUserConsentAdapter', () => { }); it('should not call CDC SDK', () => { storage.checkIfConsentExists = createSpy().and.returnValue(false); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 0 }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 0 })); service.withdrawConsent('current', 'code', 'xxxx').subscribe(); - expect(cdcUserConsentService.updateCdcConsent).not.toHaveBeenCalledWith( - false, - ['xxxx'] - ); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).not.toHaveBeenCalledWith([{ id: 'xxxx', isConsentGranted: false }]); }); it('should not call Commerce API', () => { storage.checkIfConsentExists = createSpy().and.returnValue(true); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 2 }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 2 })); service.withdrawConsent('current', 'code', 'xxxx').subscribe(); - expect(cdcUserConsentService.updateCdcConsent).toHaveBeenCalledWith( - false, - ['xxxx'] - ); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).toHaveBeenCalledWith([{ id: 'xxxx', isConsentGranted: false }]); httpMock.expectNone((req) => { return req.method === 'DELETE'; }); diff --git a/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.ts b/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.ts index 273d9fdea60..35aacc4bb33 100644 --- a/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.ts +++ b/integration-libs/cdc/root/consent-management/cdc-user-consent.adapter.ts @@ -45,7 +45,9 @@ export class CdcUserConsentAdapter extends OccUserConsentAdapter { ); } else { return this.cdcUserConsentService - .updateCdcConsent(true, [consentTemplateId]) + .updateCdcUserPreferences([ + { id: consentTemplateId, isConsentGranted: true }, + ]) .pipe( catchError((error: any) => throwError(error)), switchMap((result) => { @@ -71,7 +73,9 @@ export class CdcUserConsentAdapter extends OccUserConsentAdapter { return super.withdrawConsent(userId, consentCode); } else { return this.cdcUserConsentService - .updateCdcConsent(false, consentId ? [consentId] : []) + .updateCdcUserPreferences([ + { id: consentId ?? '', isConsentGranted: false }, + ]) .pipe( catchError((error: any) => throwError(error)), switchMap((result) => { diff --git a/integration-libs/cdc/root/consent-management/converters/cdc-preference.serializer.spec.ts b/integration-libs/cdc/root/consent-management/converters/cdc-preference.serializer.spec.ts new file mode 100644 index 00000000000..618a974e7d2 --- /dev/null +++ b/integration-libs/cdc/root/consent-management/converters/cdc-preference.serializer.spec.ts @@ -0,0 +1,48 @@ +import { TestBed } from '@angular/core/testing'; +import { CdcPreferenceSerializer } from './cdc-preference.serializer'; +import { CdcConsent } from '../model'; +const mockInput: CdcConsent[] = [ + { id: 'terms.of.use', isConsentGranted: true }, + { id: 'terms.marketing', isConsentGranted: false }, + { id: 'others.analytics', isConsentGranted: true }, +]; + +const mockOutput = { + terms: { + of: { + use: { + isConsentGranted: true, + }, + }, + marketing: { + isConsentGranted: false, + }, + }, + others: { + analytics: { + isConsentGranted: true, + }, + }, +}; +describe('CdcPreferenceSerializer', () => { + let service: CdcPreferenceSerializer; + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + declarations: [], + providers: [], + }); + service = TestBed.inject(CdcPreferenceSerializer); + TestBed.compileComponents(); + }); + it('should create service', () => { + expect(service).toBeTruthy(); + }); + describe('convert()', () => { + it('convert consent array into cdc user preference', () => { + let target = {}; + target = service.convert(mockInput, target); + expect(target).toEqual(mockOutput); + }); + }); +}); diff --git a/integration-libs/cdc/root/consent-management/converters/cdc-preference.serializer.ts b/integration-libs/cdc/root/consent-management/converters/cdc-preference.serializer.ts new file mode 100644 index 00000000000..70ff713009c --- /dev/null +++ b/integration-libs/cdc/root/consent-management/converters/cdc-preference.serializer.ts @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2025 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Injectable } from '@angular/core'; +import { Converter } from '@spartacus/core'; +import { CdcConsent } from '../model'; + +@Injectable({ providedIn: 'root' }) +export class CdcPreferenceSerializer implements Converter { + /** + * Generate CDC preferences from consent objects + * @param cdcConsents List of consents with their statuses + * @returns Serialized and deeply nested preferences object + */ + convert(source: CdcConsent[], _target?: any): any { + return source.reduce((preferences: any, cdcConsent) => { + if (!cdcConsent.id) { + return preferences; + } + const path = `${cdcConsent.id}.isConsentGranted`; + const value = cdcConsent.isConsentGranted === true; + const serializedPreference = this.convertToCdcPreference(path, value); + return this.deepMerge(preferences, serializedPreference); + }, {}); + } + + /** + * Converts a dot-separated string to a deeply nested object. + * @param path Dot-separated string representing keys + * @param value Value to set + * @returns Nested object + */ + private convertToCdcPreference(path: string, value: any): any { + return path.split('.').reduceRight((acc, key) => ({ [key]: acc }), value); + } + + /** + * Merges two objects into one. + */ + private deepMerge(target: any, source: any): any { + for (const key of Object.keys(source)) { + if ( + source[key] && + typeof source[key] === 'object' && + !Array.isArray(source[key]) + ) { + target[key] = this.deepMerge(target[key] ?? {}, source[key]); + } else { + target[key] = source[key]; + } + } + return target; + } +} diff --git a/integration-libs/cdc/root/consent-management/converters/cdc-user-preference.serializer.ts b/integration-libs/cdc/root/consent-management/converters/cdc-user-preference.serializer.ts index 49160574de2..9514bcd8351 100644 --- a/integration-libs/cdc/root/consent-management/converters/cdc-user-preference.serializer.ts +++ b/integration-libs/cdc/root/consent-management/converters/cdc-user-preference.serializer.ts @@ -7,6 +7,10 @@ import { Injectable } from '@angular/core'; import { ConsentTemplate, Converter } from '@spartacus/core'; +/** + * @deprecated since 2211-ng19.0, use class CdcPreferenceSerializer instead + */ +// CXSPA-9292: remove this service in next major release @Injectable({ providedIn: 'root' }) export class CdcUserPreferenceSerializer implements Converter diff --git a/integration-libs/cdc/root/consent-management/converters/converter.ts b/integration-libs/cdc/root/consent-management/converters/converter.ts index 00d7dd22263..582e011ef40 100644 --- a/integration-libs/cdc/root/consent-management/converters/converter.ts +++ b/integration-libs/cdc/root/consent-management/converters/converter.ts @@ -6,8 +6,17 @@ import { InjectionToken } from '@angular/core'; import { Converter, ConsentTemplate } from '@spartacus/core'; +import { CdcConsent } from '../model'; +/** + * @deprecated since 2211-ng19.0 + */ +// CXSPA-9292: remove this in next major release //maintaining target as any because 'preferences' in cdc can have any structure export const CDC_USER_PREFERENCE_SERIALIZER = new InjectionToken< Converter >('CdcUserPreferenceSerializer'); + +export const CDC_PREFERENCE_SERIALIZER = new InjectionToken< + Converter +>('CdcPreferenceSerializer'); diff --git a/integration-libs/cdc/root/consent-management/converters/index.ts b/integration-libs/cdc/root/consent-management/converters/index.ts index 350f6b3ac85..2c2728956fc 100644 --- a/integration-libs/cdc/root/consent-management/converters/index.ts +++ b/integration-libs/cdc/root/consent-management/converters/index.ts @@ -5,4 +5,5 @@ */ export * from './cdc-user-preference.serializer'; +export * from './cdc-preference.serializer'; export * from './converter'; diff --git a/integration-libs/cdc/root/consent-management/model/cdc-consent-management.model.ts b/integration-libs/cdc/root/consent-management/model/cdc-consent-management.model.ts index 64929da848a..3441b698225 100644 --- a/integration-libs/cdc/root/consent-management/model/cdc-consent-management.model.ts +++ b/integration-libs/cdc/root/consent-management/model/cdc-consent-management.model.ts @@ -26,6 +26,11 @@ export interface CdcSiteConsentTemplate { }; } +export interface CdcConsent { + id?: string; + isConsentGranted?: boolean; +} + export interface CdcLocalStorageTemplate { id: string; required: boolean; diff --git a/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.spec.ts b/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.spec.ts index a7c533cd9c8..c02fd5926e3 100644 --- a/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.spec.ts +++ b/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.spec.ts @@ -70,4 +70,14 @@ describe('CdcConsentManagementService', () => { expect(service.getCdcConsentIDs).toHaveBeenCalled(); }); }); + describe('isConsentMandatory', () => { + it('should return true if consent is mandatory', () => { + service.getCdcConsentIDs = createSpy().and.returnValue(['a']); + expect(service.isConsentMandatory('a')).toEqual(true); + }); + it('should return false if consent is not mandatory', () => { + service.getCdcConsentIDs = createSpy().and.returnValue(['a']); + expect(service.isConsentMandatory('b')).toEqual(false); + }); + }); }); diff --git a/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.ts b/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.ts index 92ec610c21e..9dc9e7bb513 100644 --- a/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.ts +++ b/integration-libs/cdc/root/consent-management/services/cdc-consent-management-component.service.ts @@ -46,4 +46,8 @@ export class CdcConsentManagementComponentService extends ConsentManagementCompo }); return consentIDs; } + + isConsentMandatory(id: string): boolean { + return this.getCdcConsentIDs(true).includes(id); + } } diff --git a/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.spec.ts b/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.spec.ts index b6dcb068455..0822a279522 100644 --- a/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.spec.ts +++ b/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.spec.ts @@ -92,7 +92,9 @@ describe('CdcUserConsentService()', () => { cdcJsService.setUserConsentPreferences = createSpy().and.returnValue( of(mockCdcSdkOutput) ); - service.updateCdcConsent(true, ['others.survey']); + service.updateCdcUserPreferences([ + { id: 'others.survey', isConsentGranted: true }, + ]); expect(cdcJsService.setUserConsentPreferences).toHaveBeenCalledWith( 'sampleuser@mail.com', 'en', @@ -119,7 +121,9 @@ describe('CdcUserConsentService()', () => { cdcJsService.setUserConsentPreferences = createSpy().and.returnValue( of(mockCdcSdkOutput) ); - service.updateCdcConsent(false, ['others.survey']); + service.updateCdcUserPreferences([ + { id: 'others.survey', isConsentGranted: false }, + ]); expect(cdcJsService.setUserConsentPreferences).toHaveBeenCalledWith( 'sampleuser@mail.com', 'en', diff --git a/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.ts b/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.ts index 17d0ffd5589..af356c6cbe5 100644 --- a/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.ts +++ b/integration-libs/cdc/root/consent-management/services/cdc-user-consent.service.ts @@ -13,9 +13,13 @@ import { import { UserProfileFacade } from '@spartacus/user/profile/root'; import { Observable, throwError } from 'rxjs'; import { CdcConsentsLocalStorageService } from './cdc-consents-local-storage.service'; -import { CDC_USER_PREFERENCE_SERIALIZER } from '../converters/converter'; +import { + CDC_PREFERENCE_SERIALIZER, + CDC_USER_PREFERENCE_SERIALIZER, +} from '../converters/converter'; import { tap } from 'rxjs/operators'; import { CdcJsService } from '../../service'; +import { CdcConsent } from '../model'; @Injectable({ providedIn: 'root' }) export class CdcUserConsentService { @@ -34,7 +38,9 @@ export class CdcUserConsentService { * @param user - If user is not passed, the logged in user id will be fetched and used. If passed, it will be considered. * @param regToken - token * @returns - returns Observable with error code and status + * @deprecated since 2211-ng19.0, use method updateCdcUserPreferences instead */ + // CXSPA-9292: remove this method in next major release updateCdcConsent( isConsentGranted: boolean, consentCodes: string[], @@ -83,6 +89,46 @@ export class CdcUserConsentService { ); } + /** + * + * @param consentCodes an array of consent ID with status + * @param user If user is not passed, the logged in user id will be fetched and used. If passed, it will be considered. + * @param regToken token + * @returns returns Observable with error code and status + */ + updateCdcUserPreferences( + consentCodes: CdcConsent[], + user?: string, + regToken?: string + ): Observable<{ errorCode: number; errorMessage: string }> { + const serializedPreference: any = this.converter.convert( + consentCodes, + CDC_PREFERENCE_SERIALIZER + ); + + let userId: string = ''; + if (user === undefined) { + userId = this.getUserID() ?? ''; + } else if (user !== undefined) { + userId = user; + } + const currentLanguage = this.getActiveLanguage(); + return this.cdcJsService + .setUserConsentPreferences( + userId, + currentLanguage, + serializedPreference, + regToken + ) + .pipe( + tap({ + error: (error) => { + throwError(error); + }, + }) + ); + } + /** * Returns logged in User ID * @returns user id diff --git a/integration-libs/cdc/root/events/cdc-event.ts b/integration-libs/cdc/root/events/cdc-event.ts index 2db5d84db19..b629e33f640 100644 --- a/integration-libs/cdc/root/events/cdc-event.ts +++ b/integration-libs/cdc/root/events/cdc-event.ts @@ -5,6 +5,7 @@ */ import { CxEvent } from '@spartacus/core'; +import { CdcConsent } from '../consent-management'; /** * Indicates the failure during the loading of the user token. @@ -26,4 +27,5 @@ export class CdcReConsentEvent extends CxEvent { consentIds: string[]; errorMessage: string; regToken: string; + preferences?: Record; } diff --git a/integration-libs/cdc/root/service/cdc-js.service.spec.ts b/integration-libs/cdc/root/service/cdc-js.service.spec.ts index a8cd092ea91..8523943da6a 100644 --- a/integration-libs/cdc/root/service/cdc-js.service.spec.ts +++ b/integration-libs/cdc/root/service/cdc-js.service.spec.ts @@ -269,7 +269,7 @@ describe('CdcJsService', () => { errorCallback: jasmine.any(Function) as any, }); expect(winRef?.nativeWindow['__gigyaConf']).toEqual({ - include: 'id_token, missing-required-fields', + include: 'id_token, missing-required-fields, preferences', }); }); diff --git a/integration-libs/cdc/root/service/cdc-js.service.ts b/integration-libs/cdc/root/service/cdc-js.service.ts index 1d3c80f02d6..4d82cc90596 100644 --- a/integration-libs/cdc/root/service/cdc-js.service.ts +++ b/integration-libs/cdc/root/service/cdc-js.service.ts @@ -125,7 +125,7 @@ export class CdcJsService implements OnDestroy { (this.winRef.nativeWindow as { [key: string]: any })[ '__gigyaConf' ] = { - include: 'id_token, missing-required-fields', + include: 'id_token, missing-required-fields, preferences', }; } } @@ -281,7 +281,8 @@ export class CdcJsService implements OnDestroy { password, response.missingRequiredFields, response.errorMessage, - response.regToken + response.regToken, + response.preferences ); } }, @@ -822,7 +823,8 @@ export class CdcJsService implements OnDestroy { password: string, reconsentIds: string[], errorMessage: string, - regToken: string + regToken: string, + preferences?: any ): void { const consentIds: string[] = []; reconsentIds.forEach((template) => { @@ -839,6 +841,7 @@ export class CdcJsService implements OnDestroy { newReConsentEvent.consentIds = consentIds; newReConsentEvent.errorMessage = errorMessage; newReConsentEvent.regToken = regToken; + newReConsentEvent.preferences = preferences; this.eventService.dispatch(newReConsentEvent); } diff --git a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.spec.ts b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.spec.ts index de863e01e7b..0c4492a9c23 100644 --- a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.spec.ts +++ b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.spec.ts @@ -5,6 +5,9 @@ import { LaunchDialogService } from '@spartacus/storefront'; import { of, throwError } from 'rxjs'; import { CdcReconsentComponentService } from './cdc-reconsent-component.service'; import createSpy = jasmine.createSpy; +const reconsentIdsWithStatus = [ + { id: 'consent.survey', isConsentGranted: true }, +]; const reconsentIds = ['consent.survey']; const userParams = { user: 'sample@user.com', @@ -19,6 +22,7 @@ class MockLaunchDialogService implements Partial { closeDialog = createSpy(); } class MockCdcUserConsentService implements Partial { + updateCdcUserPreferences = createSpy(); updateCdcConsent = createSpy(); } class MockCdcJsService implements Partial { @@ -94,9 +98,10 @@ describe('CdcReconsentComponentService', () => { cdcJsService.loginUserWithoutScreenSet = createSpy().and.returnValue( of({ status: 'ok' }) ); - cdcUserConsentService.updateCdcConsent = createSpy().and.returnValue( - of({ errorCode: 404, errorMessage: 'error during process' }) - ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue( + of({ errorCode: 404, errorMessage: 'error during process' }) + ); globalMessageService.add = createSpy().and.stub(); service.saveConsentAndLogin(reconsentIds, userParams); expect(cdcJsService.didLoad).toHaveBeenCalled(); @@ -105,6 +110,60 @@ describe('CdcReconsentComponentService', () => { expect(globalMessageService.add).toHaveBeenCalled(); }); }); + describe('savePreferencesAndLogin', () => { + it('on successful save of re-consent and re-login', () => { + cdcJsService.didLoad = createSpy().and.returnValue(of(true)); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue(of({ errorCode: 0, errorMessage: '' })); + cdcJsService.loginUserWithoutScreenSet = createSpy().and.returnValue( + of({ status: 'OK' }) + ); + spyOn(service, 'handleReconsentUpdateError').and.stub(); + service.savePreferencesAndLogin(reconsentIdsWithStatus, userParams); + expect(cdcJsService.didLoad).toHaveBeenCalled(); + expect(cdcJsService.loginUserWithoutScreenSet).toHaveBeenCalledWith( + userParams.user, + userParams.password + ); + expect(cdcUserConsentService.updateCdcUserPreferences).toHaveBeenCalled(); + expect(service.handleReconsentUpdateError).not.toHaveBeenCalled(); + }); + it('on error during save of re-consent', () => { + cdcJsService.didLoad = createSpy().and.returnValue(of(true)); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue( + throwError({ errorCode: 404, errorMessage: 'error during process' }) + ); + cdcJsService.loginUserWithoutScreenSet = createSpy().and.returnValue( + of({ status: 'OK' }) + ); + launchDialogService.closeDialog = createSpy().and.stub(); + spyOn(service, 'handleReconsentUpdateError').and.stub(); + service.savePreferencesAndLogin(reconsentIdsWithStatus, userParams); + expect(cdcJsService.didLoad).toHaveBeenCalled(); + expect(cdcUserConsentService.updateCdcUserPreferences).toHaveBeenCalled(); + expect(cdcJsService.loginUserWithoutScreenSet).not.toHaveBeenCalled(); + expect(service.handleReconsentUpdateError).toHaveBeenCalled(); + }); + it('should stop processing in case of cdc load failure', () => { + cdcJsService.didLoad = createSpy().and.returnValue(of(false)); + cdcJsService.loginUserWithoutScreenSet = createSpy().and.returnValue( + of({ status: 'ok' }) + ); + cdcUserConsentService.updateCdcUserPreferences = + createSpy().and.returnValue( + of({ errorCode: 404, errorMessage: 'error during process' }) + ); + globalMessageService.add = createSpy().and.stub(); + service.savePreferencesAndLogin(reconsentIdsWithStatus, userParams); + expect(cdcJsService.didLoad).toHaveBeenCalled(); + expect(cdcJsService.loginUserWithoutScreenSet).not.toHaveBeenCalled(); + expect( + cdcUserConsentService.updateCdcUserPreferences + ).not.toHaveBeenCalled(); + expect(globalMessageService.add).toHaveBeenCalled(); + }); + }); describe('handleReconsentUpdateError', () => { it('should close dialog and raise error', () => { launchDialogService.closeDialog = createSpy().and.stub(); diff --git a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.ts b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.ts index f77bee4ad9a..647c2f45863 100644 --- a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.ts +++ b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-component.service.ts @@ -8,7 +8,11 @@ import { Injectable, OnDestroy } from '@angular/core'; import { GlobalMessageService, GlobalMessageType } from '@spartacus/core'; import { Subscription } from 'rxjs'; import { LaunchDialogService } from '@spartacus/storefront'; -import { CdcJsService, CdcUserConsentService } from '@spartacus/cdc/root'; +import { + CdcConsent, + CdcJsService, + CdcUserConsentService, +} from '@spartacus/cdc/root'; @Injectable({ providedIn: 'root' }) export class CdcReconsentComponentService implements OnDestroy { @@ -20,11 +24,63 @@ export class CdcReconsentComponentService implements OnDestroy { ) {} protected subscription: Subscription = new Subscription(); + /** + * saves the preferences given in reconsent pop-up and triggers a re-login + * @param consents array of consent ID with status + * @param userParams data from login session + */ + savePreferencesAndLogin(consents: CdcConsent[], userParams: any) { + this.subscription.add( + this.cdcJsService.didLoad().subscribe((cdcLoaded) => { + if (cdcLoaded) { + this.cdcUserConsentService + .updateCdcUserPreferences( + consents, + userParams?.user, + userParams?.regToken + ) + .subscribe({ + next: (result) => { + if (result?.errorCode === 0) { + this.cdcJsService + .loginUserWithoutScreenSet( + userParams.user, + userParams.password + ) + .subscribe(() => { + this.launchDialogService.closeDialog( + 'relogin successful' + ); + }); + } + }, + error: (error) => { + this.handleReconsentUpdateError( + 'Reconsent Error', + error?.message + ); + }, + }); + } else { + // CDC Gigya SDK not loaded, show error to the user + this.globalMessageService.add( + { + key: 'errorHandlers.scriptFailedToLoad', + }, + GlobalMessageType.MSG_TYPE_ERROR + ); + } + }) + ); + } + /** * saves the consent given from reconsent pop-up and triggers a re-login * @param consentId - array of consent IDs * @param userParams - data from login session + * @deprecated since 2211-ng19.0, use method savePreferencesAndLogin instead */ + // CXSPA-9292: remove this method in next major release saveConsentAndLogin(consentId: string[], userParams: any) { this.subscription.add( this.cdcJsService.didLoad().subscribe((cdcLoaded) => { diff --git a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.spec.ts b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.spec.ts index 1e671c851fb..67d453bbeae 100644 --- a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.spec.ts +++ b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.spec.ts @@ -29,6 +29,7 @@ mockEvent.password = 'password'; mockEvent.consentIds = ['consent.survey', 'terms.of.use']; mockEvent.errorMessage = 'Account Registration Pending'; mockEvent.regToken = 'xcEfsd123'; +mockEvent.preferences = { terms: { isConsentGranted: true } }; describe('CdcReconsentDialogEventListener', () => { let listener: CdcReconsentDialogEventListener; let launchDialogService: LaunchDialogService; @@ -72,6 +73,7 @@ describe('CdcReconsentDialogEventListener', () => { consentIds: mockEvent.consentIds, errorMessage: mockEvent.errorMessage, regToken: mockEvent.regToken, + preferences: mockEvent.preferences, } ); }); diff --git a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.ts b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.ts index 1f7dcb8c492..af3defad011 100644 --- a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.ts +++ b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent-dialogue-event.listener.ts @@ -37,6 +37,7 @@ export class CdcReconsentDialogEventListener implements OnDestroy { consentIds: event.consentIds, errorMessage: event.errorMessage, regToken: event.regToken, + preferences: event.preferences, }; const dialog = this.launchDialogService.openDialog( LAUNCH_CALLER.CDC_RECONSENT, diff --git a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent.component.html b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent.component.html index fc75ba67ec9..8fb36639076 100644 --- a/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent.component.html +++ b/integration-libs/cdc/user-account/login-form/reconsent/cdc-reconsent.component.html @@ -1,12 +1,12 @@