From 82addf5aec2c0af316a71d83f975447948b00d45 Mon Sep 17 00:00:00 2001 From: Melody-zhou-512 <140146828+Melody-zhou-512@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:24:27 +0800 Subject: [PATCH 01/60] draft draft --- .../b2b-unit-selection/assets/public_api.ts | 7 + .../translations/en/b2b-unit-selection.json | 13 ++ .../assets/translations/en/index.ts | 9 + .../assets/translations/translations.ts | 14 ++ .../b2b-unit-selection.module.ts | 19 ++ .../b2b-unit-selection-components.module.ts | 23 +++ .../b2b-unit-selection-dialog.component.html | 52 +++++ .../b2b-unit-selection-dialog.component.ts | 114 +++++++++++ .../b2b-unit-selector-outlet.component.ts | 26 +++ .../b2b-unit-selector.component.html | 19 ++ .../b2b-unit-selector.component.ts | 45 +++++ ...efault-b2b-unit-selection-layout.config.ts | 24 +++ .../components/public_api.ts | 11 ++ .../core/b2b-unit-selection-core.module.ts | 15 ++ .../connectors/b2b-unit-selection.adapter.ts | 26 +++ .../b2b-unit-selection.connector.ts | 27 +++ .../b2b-unit-selection/core/public_api.ts | 11 ++ .../b2b-unit-selector-state.service.ts | 36 ++++ .../actions/b2b-unit-selection.actions.ts | 62 ++++++ .../core/store/actions/index.ts | 7 + .../store/b2b-unit-selection-store.module.ts | 14 ++ .../effects/b2b-unit-selection.effects.ts | 179 ++++++++++++++++++ .../occ-b2b-unit-selection.adapter.ts | 72 +++++++ .../occ/b2b-unit-selection-occ.module.ts | 22 +++ .../default-occ-b2b-unit-selection-config.ts | 19 ++ .../b2b-unit-selection/occ/model/index.ts | 7 + .../occ-b2b-unit-selection-endpoints.model.ts | 31 +++ .../b2b-unit-selection/occ/public_api.ts | 10 + .../b2b-unit-selection/public_api.ts | 11 ++ .../root/b2b-aware-auth-redirect.service.ts | 32 ++++ .../root/b2b-login-meta-reducer.ts | 46 +++++ .../root/b2b-redirect-coordinator.service.ts | 43 +++++ .../root/b2b-redirect-patcher.ts | 78 ++++++++ .../root/b2b-unit-selection-root.module.ts | 53 ++++++ .../b2b-unit-selection/root/feature-name.ts | 8 + .../root/model/augmented-core.model.ts | 15 ++ .../b2b-unit-selection/root/model/index.ts | 7 + .../b2b-unit-selection/root/public_api.ts | 9 + .../storefrontapp/src/app/app.component.ts | 8 +- ...ation-b2b-unit-selection-feature.module.ts | 39 ++++ .../spartacus/spartacus-features.module.ts | 2 + tsconfig.json | 18 ++ 42 files changed, 1281 insertions(+), 2 deletions(-) create mode 100644 feature-libs/organization/b2b-unit-selection/assets/public_api.ts create mode 100644 feature-libs/organization/b2b-unit-selection/assets/translations/en/b2b-unit-selection.json create mode 100644 feature-libs/organization/b2b-unit-selection/assets/translations/en/index.ts create mode 100644 feature-libs/organization/b2b-unit-selection/assets/translations/translations.ts create mode 100644 feature-libs/organization/b2b-unit-selection/b2b-unit-selection.module.ts create mode 100644 feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-components.module.ts create mode 100644 feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.html create mode 100644 feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.ts create mode 100644 feature-libs/organization/b2b-unit-selection/components/b2b-unit-selector-outlet/b2b-unit-selector-outlet.component.ts create mode 100644 feature-libs/organization/b2b-unit-selection/components/b2b-unit-selector/b2b-unit-selector.component.html create mode 100644 feature-libs/organization/b2b-unit-selection/components/b2b-unit-selector/b2b-unit-selector.component.ts create mode 100644 feature-libs/organization/b2b-unit-selection/components/default-b2b-unit-selection-layout.config.ts create mode 100644 feature-libs/organization/b2b-unit-selection/components/public_api.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/b2b-unit-selection-core.module.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/connectors/b2b-unit-selection.adapter.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/connectors/b2b-unit-selection.connector.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/public_api.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/services/b2b-unit-selector-state.service.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/store/actions/b2b-unit-selection.actions.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/store/actions/index.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/store/b2b-unit-selection-store.module.ts create mode 100644 feature-libs/organization/b2b-unit-selection/core/store/effects/b2b-unit-selection.effects.ts create mode 100644 feature-libs/organization/b2b-unit-selection/occ/adapters/occ-b2b-unit-selection.adapter.ts create mode 100644 feature-libs/organization/b2b-unit-selection/occ/b2b-unit-selection-occ.module.ts create mode 100644 feature-libs/organization/b2b-unit-selection/occ/config/default-occ-b2b-unit-selection-config.ts create mode 100644 feature-libs/organization/b2b-unit-selection/occ/model/index.ts create mode 100644 feature-libs/organization/b2b-unit-selection/occ/model/occ-b2b-unit-selection-endpoints.model.ts create mode 100644 feature-libs/organization/b2b-unit-selection/occ/public_api.ts create mode 100644 feature-libs/organization/b2b-unit-selection/public_api.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/b2b-aware-auth-redirect.service.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/b2b-login-meta-reducer.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/b2b-redirect-coordinator.service.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/b2b-redirect-patcher.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/b2b-unit-selection-root.module.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/feature-name.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/model/augmented-core.model.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/model/index.ts create mode 100644 feature-libs/organization/b2b-unit-selection/root/public_api.ts create mode 100644 projects/storefrontapp/src/app/spartacus/features/organization/organization-b2b-unit-selection-feature.module.ts diff --git a/feature-libs/organization/b2b-unit-selection/assets/public_api.ts b/feature-libs/organization/b2b-unit-selection/assets/public_api.ts new file mode 100644 index 00000000000..bae44a4d359 --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/assets/public_api.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: 2026 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './translations/translations'; diff --git a/feature-libs/organization/b2b-unit-selection/assets/translations/en/b2b-unit-selection.json b/feature-libs/organization/b2b-unit-selection/assets/translations/en/b2b-unit-selection.json new file mode 100644 index 00000000000..e939868adb1 --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/assets/translations/en/b2b-unit-selection.json @@ -0,0 +1,13 @@ +{ + "b2bUnitSelection": { + "dialog": { + "title": "Confirm B2B Unit", + "description": "Confirm the B2B unit you want to act on behalf of.", + "unitLabel": "B2B Unit", + "confirm": "Confirm" + }, + "selector": { + "label": "Company" + } + } +} diff --git a/feature-libs/organization/b2b-unit-selection/assets/translations/en/index.ts b/feature-libs/organization/b2b-unit-selection/assets/translations/en/index.ts new file mode 100644 index 00000000000..3bf9d093d05 --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/assets/translations/en/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import b2bUnitSelection from './b2b-unit-selection.json'; + +export const en = { b2bUnitSelection }; diff --git a/feature-libs/organization/b2b-unit-selection/assets/translations/translations.ts b/feature-libs/organization/b2b-unit-selection/assets/translations/translations.ts new file mode 100644 index 00000000000..026cb2a5dbc --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/assets/translations/translations.ts @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2026 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TranslationChunksConfig } from '@spartacus/core'; + +export const b2bUnitSelectionTranslationChunksConfig: TranslationChunksConfig = + { + b2bUnitSelection: ['b2bUnitSelection'], + }; + +export { en as b2bUnitSelectionTranslationsEn } from './en/index'; diff --git a/feature-libs/organization/b2b-unit-selection/b2b-unit-selection.module.ts b/feature-libs/organization/b2b-unit-selection/b2b-unit-selection.module.ts new file mode 100644 index 00000000000..3f22dcaabe1 --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/b2b-unit-selection.module.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { B2bUnitSelectionCoreModule } from './core/b2b-unit-selection-core.module'; +import { B2bUnitSelectionOccModule } from './occ/b2b-unit-selection-occ.module'; +import { B2bUnitSelectionComponentsModule } from './components/b2b-unit-selection-components.module'; + +@NgModule({ + imports: [ + B2bUnitSelectionCoreModule, + B2bUnitSelectionOccModule, + B2bUnitSelectionComponentsModule, + ], +}) +export class B2bUnitSelectionModule {} diff --git a/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-components.module.ts b/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-components.module.ts new file mode 100644 index 00000000000..f6ecf08a8db --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-components.module.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2026 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NgModule } from '@angular/core'; +import { provideDefaultConfig } from '@spartacus/core'; +import { B2bUnitSelectionDialogComponent } from './b2b-unit-selection-dialog/b2b-unit-selection-dialog.component'; +import { B2bUnitSelectorComponent } from './b2b-unit-selector/b2b-unit-selector.component'; +import { B2bUnitSelectorOutletComponent } from './b2b-unit-selector-outlet/b2b-unit-selector-outlet.component'; +import { defaultB2bUnitSelectionLayoutConfig } from './default-b2b-unit-selection-layout.config'; + +@NgModule({ + imports: [ + B2bUnitSelectionDialogComponent, + B2bUnitSelectorComponent, + B2bUnitSelectorOutletComponent, + ], + exports: [B2bUnitSelectorOutletComponent], + providers: [provideDefaultConfig(defaultB2bUnitSelectionLayoutConfig)], +}) +export class B2bUnitSelectionComponentsModule {} diff --git a/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.html b/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.html new file mode 100644 index 00000000000..97addbd91bb --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.html @@ -0,0 +1,52 @@ + diff --git a/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.ts b/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.ts new file mode 100644 index 00000000000..18f4047f0d0 --- /dev/null +++ b/feature-libs/organization/b2b-unit-selection/components/b2b-unit-selection-dialog/b2b-unit-selection-dialog.component.ts @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2026 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CommonModule } from '@angular/common'; +import { + AfterViewInit, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, +} from '@angular/core'; +import { + FormControl, + FormGroup, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { B2BUnit, TranslatePipe, UserIdService } from '@spartacus/core'; +import { + FocusConfig, + FocusDirective, + FormErrorsModule, + LaunchDialogService, +} from '@spartacus/storefront'; +import { Subscription } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { SetDefaultOrgUnit } from '../../core/store/actions/b2b-unit-selection.actions'; + +@Component({ + selector: 'cx-b2b-unit-selection-dialog', + templateUrl: './b2b-unit-selection-dialog.component.html', + imports: [ + CommonModule, + ReactiveFormsModule, + FocusDirective, + TranslatePipe, + FormErrorsModule, + ], +}) +export class B2bUnitSelectionDialogComponent + implements OnInit, AfterViewInit, OnDestroy +{ + orgUnits: B2BUnit[] = []; + /** ngOnInit 中计算好,ngAfterViewInit 中使用(届时 *ngFor 已完成渲染)。 */ + private preselectUid: string | null = null; + protected subscriptions = new Subscription(); + + focusConfig: FocusConfig = { + trap: true, + block: true, + autofocus: 'select', + focusOnEscape: false, + }; + + form = new FormGroup({ + selectedUnit: new FormControl(null, [Validators.required]), + }); + + constructor( + protected launchDialogService: LaunchDialogService, + protected userIdService: UserIdService, + protected store: Store, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.subscriptions.add( + this.launchDialogService.data$.pipe(take(1)).subscribe( + (data: { orgUnits: B2BUnit[]; defaultUnitUid?: string }) => { + this.orgUnits = data?.orgUnits ?? []; + // OCC /orgUsers/{userId}/orgUnits 只返回 name,不含 uid。 + // 用 GET /orgUsers/{userId} 返回的 orgUnit.name(即 defaultUnitUid 字段, + // 现在存的是 name)与列表中的 name 做匹配来确定预选项。 + const matched = this.orgUnits.find( + (u) => u.name === data?.defaultUnitUid + ); + this.preselectUid = matched?.name ?? this.orgUnits[0]?.name ?? null; + } + ) + ); + } + + ngAfterViewInit(): void { + // 视图初始化完成后 *ngFor 已将所有