diff --git a/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html b/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html index cea53d5837f..91b559d8ff0 100644 --- a/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html +++ b/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html @@ -23,9 +23,14 @@ + {{ + disableReason + }} + maxQuantity" + [disabled]="quantity <= 0 || quantity > maxQuantity || disableReason" > { - this.productCode = product.code ?? ''; - this.setStockInfo(product); - this.cd.markForCheck(); - }); + ) + .pipe(filter(isNotNullable)) + .subscribe((product) => { + this.productCode = product.code ?? ''; + this.setStockInfo(product); + this.cd.markForCheck(); + }) + ); } } @@ -239,9 +241,24 @@ export class AddToCartComponent implements OnInit, OnDestroy { return newEvent; } + onPickupOptionsCompLoaded() { + this.subscription.add( + this.pickupOptionCompRef.instance.intendedPickupChange.subscribe( + (intendedPickupLocation: AugmentedPointOfService | undefined) => { + if ( + intendedPickupLocation?.pickupOption === 'pickup' && + !intendedPickupLocation.displayName + ) { + this.disableReason = 'Store for Pick Up is not selected'; + } else { + this.disableReason = null; + } + } + ) + ); + } + ngOnDestroy() { - if (this.subscription) { - this.subscription.unsubscribe(); - } + this.subscription.unsubscribe(); } } diff --git a/feature-libs/cart/base/styles/components/_add-to-cart.scss b/feature-libs/cart/base/styles/components/_add-to-cart.scss index 9c430616f37..dd6eaa66651 100644 --- a/feature-libs/cart/base/styles/components/_add-to-cart.scss +++ b/feature-libs/cart/base/styles/components/_add-to-cart.scss @@ -29,4 +29,50 @@ .buyItAgainLink { color: var(--cx-color-primary); } + + .cx-invalid-message { + font-size: 14px; + margin: 6px 0; + padding-inline-start: 25px; + position: relative; + word-break: break-word; + + @include forFeature('a11yImproveContrast') { + @include type('7'); + } + + &::before, + &::after { + position: absolute; + left: 0; + top: 0; + bottom: 0; + margin: auto; + width: 20px; + height: 20px; + } + + &::before { + content: ''; + background-color: var(--cx-color-danger); + border-radius: 50%; + } + + &::after { + content: '!'; + // TODO: (CXSPA-7588) - Remove feature flag next major release + @include forFeature('a11yFormErrorMuteIcon') { + content: '!' / ''; + } + color: var(--cx-color-inverse); + font-weight: var(--cx-font-weight-bold); + text-align: center; + line-height: 20px; + } + + &:focus { + box-shadow: none; + -webkit-box-shadow: none; + } + } } diff --git a/feature-libs/pickup-in-store/components/container/pdp-pickup-options-container/pdp-pickup-options-container.component.ts b/feature-libs/pickup-in-store/components/container/pdp-pickup-options-container/pdp-pickup-options-container.component.ts index 9521bb3af46..98713b94bde 100644 --- a/feature-libs/pickup-in-store/components/container/pdp-pickup-options-container/pdp-pickup-options-container.component.ts +++ b/feature-libs/pickup-in-store/components/container/pdp-pickup-options-container/pdp-pickup-options-container.component.ts @@ -8,8 +8,10 @@ import { Component, ElementRef, inject, + EventEmitter, OnDestroy, OnInit, + Output, ViewChild, ViewContainerRef, } from '@angular/core'; @@ -63,6 +65,9 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy { * The 'triggerElement' is passed through 'PickupOptionChange' event instead. */ @ViewChild('open') element: ElementRef; + @Output() intendedPickupChange = new EventEmitter< + AugmentedPointOfService | undefined + >(); subscription = new Subscription(); availableForPickup = false; @@ -70,7 +75,6 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy { pickupOption$: Observable; intendedPickupLocation$: Observable; private productCode: string; - private displayNameIsSet = false; private featureConfigService = inject(FeatureConfigService); constructor( @@ -135,8 +139,7 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy { }) ) ) - ), - tap(() => (this.displayNameIsSet = true)) + ) ); this.intendedPickupLocation$ = this.currentProductService.getProduct().pipe( @@ -147,6 +150,10 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy { ) ); + this.subscription.add( + this.intendedPickupLocation$.subscribe(this.intendedPickupChange) + ); + this.subscription.add( combineLatest([ productCode$, @@ -206,28 +213,16 @@ export class PdpPickupOptionsContainerComponent implements OnInit, OnDestroy { this.featureConfigService.isEnabled('a11yDialogTriggerRefocus') && typeof event === 'object' ) { - const { option, triggerElement = undefined } = event; + const { option } = event; this.intendedPickupLocationService.setPickupOption( this.productCode, option ); - if (option === 'delivery') { - return; - } - if (!this.displayNameIsSet) { - this.openDialog(triggerElement); - } } else if (typeof event === 'string') { this.intendedPickupLocationService.setPickupOption( this.productCode, event ); - if (event === 'delivery') { - return; - } - if (!this.displayNameIsSet) { - this.openDialog(); - } } } } diff --git a/feature-libs/pickup-in-store/components/container/pickup-option-dialog/pickup-option-dialog.component.ts b/feature-libs/pickup-in-store/components/container/pickup-option-dialog/pickup-option-dialog.component.ts index cf559ddb791..7fb00b536a4 100644 --- a/feature-libs/pickup-in-store/components/container/pickup-option-dialog/pickup-option-dialog.component.ts +++ b/feature-libs/pickup-in-store/components/container/pickup-option-dialog/pickup-option-dialog.component.ts @@ -29,7 +29,7 @@ import { } from '@spartacus/storefront'; import { Observable, Subscription } from 'rxjs'; -import { filter, map, take, tap } from 'rxjs/operators'; +import { filter, tap } from 'rxjs/operators'; /** * The dialog box to select the pickup location for a product. @@ -164,25 +164,6 @@ export class PickupOptionDialogComponent implements OnInit, OnDestroy { close(reason: string): void { this.launchDialogService.closeDialog(reason); if (reason === this.CLOSE_WITHOUT_SELECTION) { - this.intendedPickupLocationService - .getIntendedLocation(this.productCode) - .pipe( - filter( - (store: AugmentedPointOfService | undefined) => - typeof store !== 'undefined' - ), - map((store) => store as AugmentedPointOfService), - filter((store) => !store.name), - take(1), - tap(() => - this.intendedPickupLocationService.setPickupOption( - this.productCode, - 'delivery' - ) - ) - ) - .subscribe(); - this.pickupOptionFacade.setPickupOption(this.entryNumber, 'delivery'); return; } this.subscription.add( diff --git a/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.html b/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.html index 2c2b3da2cfa..e0ff3e6dfdb 100644 --- a/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.html +++ b/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.html @@ -1,159 +1,28 @@ - - {{ 'pickupOptions.legend' | cxTranslate }} - - - - {{ 'pickupOptions.delivery' | cxTranslate }} - - - - - - - {{ 'pickupOptions.pickup' | cxTranslate - }}: - {{ - displayPickupLocation - }} - - | - - {{ - (displayPickupLocation - ? 'pickupOptions.changeStore' - : 'pickupOptions.selectStore' - ) | cxTranslate - }} - - {{ - (displayPickupLocation - ? 'pickupOptions.changeStore' - : 'pickupOptions.selectStore' - ) | cxTranslate - }} - - - - - - - - - {{ 'pickupOptions.delivery' | cxTranslate }} - - - - - - - {{ 'pickupOptions.pickup' | cxTranslate - }}: - {{ - displayPickupLocation - }} - - | - - {{ - (displayPickupLocation - ? 'pickupOptions.changeStore' - : 'pickupOptions.selectStore' - ) | cxTranslate - }} - - {{ - (displayPickupLocation - ? 'pickupOptions.changeStore' - : 'pickupOptions.selectStore' - ) | cxTranslate - }} - - - - + + +Free return + + + {{ displayPickupLocation }} | + + + + + {{ + (displayPickupLocation + ? 'pickupOptions.changeStore' + : 'pickupOptions.selectStore' + ) | cxTranslate + }} + + diff --git a/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.ts b/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.ts index a3a310639c3..8edcd28950f 100644 --- a/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.ts +++ b/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.component.ts @@ -5,19 +5,34 @@ */ import { + AfterViewInit, + ChangeDetectorRef, Component, ElementRef, EventEmitter, inject, Input, OnChanges, + OnDestroy, Output, ViewChild, + TemplateRef, } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { FeatureConfigService, useFeatureStyles } from '@spartacus/core'; import { PickupOption } from '@spartacus/pickup-in-store/root'; +import { TabComponent } from 'projects/storefrontlib/cms-components/content/tab/tab.component'; +import { + TAB_MODE, + Tab, + TabConfig, +} from 'projects/storefrontlib/cms-components/content/tab/tab.model'; +import { Subscription, take } from 'rxjs'; +enum PickupOptionsTabs { + 'DELIVERY', + 'PICKUP', +} /** * The presentational component of a pair of radio buttons for pickup options for a product. */ @@ -25,7 +40,10 @@ import { PickupOption } from '@spartacus/pickup-in-store/root'; selector: 'cx-pickup-options', templateUrl: './pickup-options.component.html', }) -export class PickupOptionsComponent implements OnChanges { +export class PickupOptionsComponent + implements OnChanges, AfterViewInit, OnDestroy +{ + protected subscription = new Subscription(); /** The selected option, either `'pickup'` or `'delivery'`. */ @Input() selectedOption: PickupOption; /** The location to display in the pickup option. */ @@ -54,7 +72,17 @@ export class PickupOptionsComponent implements OnChanges { pickupOptionsForm = new FormGroup({ pickupOption: new FormControl(null), }); + tabs: Tab[]; + tabConfig: TabConfig = { + label: 'pickupOptions.legend', + openTabs: [PickupOptionsTabs.DELIVERY], + }; + + @ViewChild('deliveryTabPanel') deliveryTabPanel: TemplateRef; + @ViewChild('pickupTabPanel') pickupTabPanel: TemplateRef; + @ViewChild(TabComponent) tabComponent: TabComponent | undefined; + protected cdr = inject(ChangeDetectorRef); constructor() { useFeatureStyles('a11yDeliveryMethodFieldset'); } @@ -65,6 +93,46 @@ export class PickupOptionsComponent implements OnChanges { } this.pickupOptionsForm.markAllAsTouched(); this.pickupOptionsForm.get('pickupOption')?.setValue(this.selectedOption); + this.onSelectedOptionChange(); + } + + protected onSelectedOptionChange() { + if (!this.tabComponent) return; + this.tabComponent.openTabs$.pipe(take(1)).subscribe((openTabs) => { + const openedTab = openTabs[0]; + const shouldBeOpened = + this.selectedOption === 'delivery' + ? PickupOptionsTabs.DELIVERY + : PickupOptionsTabs.PICKUP; + if (openedTab !== shouldBeOpened) { + this.tabComponent?.select(shouldBeOpened, TAB_MODE.TAB); + } + }); + } + + ngAfterViewInit() { + this.tabs = [ + { + header: 'Ship it', + content: this.deliveryTabPanel, + id: PickupOptionsTabs.DELIVERY, + }, + { + header: 'Free Pickup In Store', + content: this.pickupTabPanel, + id: PickupOptionsTabs.PICKUP, + }, + ]; + this.cdr.detectChanges(); + this.subscription.add( + this.tabComponent?.openTabs$.subscribe((openTabs) => { + // open tabs should have one tab opened for mode "TAB" + const openedTab = openTabs[0]; + const selectedOption = + openedTab === PickupOptionsTabs.DELIVERY ? 'delivery' : 'pickup'; + this.onPickupOptionChange(selectedOption); + }) + ); } /** Emit a new selected option. */ @@ -89,4 +157,8 @@ export class PickupOptionsComponent implements OnChanges { // Return false to stop `onPickupOptionChange` being called after this return false; } + + ngOnDestroy(): void { + this.subscription.unsubscribe(); + } } diff --git a/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.module.ts b/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.module.ts index 5ce387548a8..297cd96c20c 100644 --- a/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.module.ts +++ b/feature-libs/pickup-in-store/components/presentational/pickup-options/pickup-options.module.ts @@ -9,6 +9,7 @@ import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { FeaturesConfigModule, I18nModule } from '@spartacus/core'; import { PickupOptionsComponent } from './pickup-options.component'; +import { TabModule } from 'projects/storefrontlib/cms-components/content/tab/tab.module'; @NgModule({ imports: [ @@ -16,6 +17,7 @@ import { PickupOptionsComponent } from './pickup-options.component'; I18nModule, ReactiveFormsModule, FeaturesConfigModule, + TabModule, ], declarations: [PickupOptionsComponent], exports: [PickupOptionsComponent],
- {{ 'pickupOptions.pickup' | cxTranslate - }}: - {{ - displayPickupLocation - }} - - | - - {{ - (displayPickupLocation - ? 'pickupOptions.changeStore' - : 'pickupOptions.selectStore' - ) | cxTranslate - }} - - {{ - (displayPickupLocation - ? 'pickupOptions.changeStore' - : 'pickupOptions.selectStore' - ) | cxTranslate - }} -