From 195ac73aede8a7cbb050aee925e63eb02b31f6cf Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 21 Aug 2026 15:50:12 -0300 Subject: [PATCH] fix(checkout): disable cart access --- .../store-header/store-header.component.html | 11 +++-- .../store-header/store-header.component.ts | 9 +++++ .../store-layout/store-layout.component.html | 5 ++- .../store-layout.component.spec.ts | 30 ++++++++++++++ .../store-layout/store-layout.component.ts | 40 ++++++++++++++++++- .../cart-icon/cart-icon.component.html | 2 + .../cart-icon/cart-icon.component.scss | 15 +++++-- .../cart-icon/cart-icon.component.spec.ts | 8 ++-- .../cart-icon/cart-icon.component.ts | 6 ++- 9 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/app/core/layout/store-layout/store-header/store-header.component.html b/src/app/core/layout/store-layout/store-header/store-header.component.html index 975deca..189ede0 100644 --- a/src/app/core/layout/store-layout/store-header/store-header.component.html +++ b/src/app/core/layout/store-layout/store-header/store-header.component.html @@ -79,10 +79,13 @@ @if (displayCart()) { } diff --git a/src/app/core/layout/store-layout/store-header/store-header.component.ts b/src/app/core/layout/store-layout/store-header/store-header.component.ts index 3edd038..9d7723b 100644 --- a/src/app/core/layout/store-layout/store-header/store-header.component.ts +++ b/src/app/core/layout/store-layout/store-header/store-header.component.ts @@ -39,6 +39,7 @@ export class StoreHeaderComponent { readonly displayCategories = input(true); readonly displaySeachBar = input(true); readonly displayCart = input(true); + readonly cartDisabled = input(false); readonly cartClick = output(); readonly ticketsClick = output(); readonly loginClick = output(); @@ -53,6 +54,14 @@ export class StoreHeaderComponent { protected readonly showSearchError = signal(false); protected readonly searchControl = new FormControl('', { nonNullable: true }); + protected onCartClick(): void { + if (this.cartDisabled()) { + return; + } + + this.cartClick.emit(); + } + @HostListener('document:click', ['$event']) protected onDocumentClick(event: MouseEvent): void { if (!this.isUserDropdownOpen() && !this.isCategoryDropdownOpen() && !this.isMobileMenuOpen()) { diff --git a/src/app/core/layout/store-layout/store-layout.component.html b/src/app/core/layout/store-layout/store-layout.component.html index d37b270..896168b 100644 --- a/src/app/core/layout/store-layout/store-layout.component.html +++ b/src/app/core/layout/store-layout/store-layout.component.html @@ -12,7 +12,8 @@ [displayCategories]="tenant()?.display_categories ?? true" [displaySeachBar]="tenant()?.display_seach_bar ?? true" [displayCart]="displayCart()" - (cartClick)="isCartOpen.set(!isCartOpen())" + [cartDisabled]="isCheckoutRoute()" + (cartClick)="onCartClick()" (ticketsClick)="onTicketsClick()" (loginClick)="onLoginClick()" (logoutClick)="onLogoutClick()" @@ -20,7 +21,7 @@ (categorySelect)="onCategorySelect($event)" /> - @if (displayCart() && isCartOpen()) { + @if (displayCart() && !isCheckoutRoute() && isCartOpen()) {
{ imports: [StoreLayoutComponent], providers: [ provideRouter([]), + { provide: UrlSerializer, useClass: TenantUrlSerializer }, { provide: ActivatedRoute, useValue: { queryParamMap: queryParamMapState.asObservable() }, @@ -240,6 +243,33 @@ describe('StoreLayoutComponent', () => { expect((fixture.nativeElement as HTMLElement).querySelector('app-cart')).not.toBeNull(); }); + it('disables the cart icon and prevents opening the popup during checkout with a tenant base path', () => { + tenantState.set({ ...tenant, base_path: 'fiesta' }); + const router = TestBed.inject(Router); + vi.spyOn(router, 'url', 'get').mockReturnValue('/fiesta/checkout?purchase=25'); + + const fixture = TestBed.createComponent(StoreLayoutComponent); + fixture.detectChanges(); + + const element = fixture.nativeElement as HTMLElement; + const cartButton = element.querySelector('app-cart-icon button'); + const header = fixture.debugElement.query(By.directive(StoreHeaderComponent)); + + expect(cartButton?.disabled).toBe(true); + expect(cartButton?.getAttribute('aria-label')).toBe( + 'Carrito no disponible durante el checkout', + ); + expect(element.querySelector('[data-testid="cart-badge"]')).toBeNull(); + + header.componentInstance.cartClick.emit(); + queryParamMapState.next(convertToParamMap({ openCart: 'true' })); + fixture.detectChanges(); + + expect((fixture.componentInstance as any).isCartOpen()).toBe(false); + expect(element.querySelector('app-cart')).toBeNull(); + expect(element.querySelector('.store-layout__cart-overlay')).toBeNull(); + }); + it('hides the configured header elements when the tenant disables them', () => { tenantState.set({ ...tenant, diff --git a/src/app/core/layout/store-layout/store-layout.component.ts b/src/app/core/layout/store-layout/store-layout.component.ts index b5740d0..53e8b10 100644 --- a/src/app/core/layout/store-layout/store-layout.component.ts +++ b/src/app/core/layout/store-layout/store-layout.component.ts @@ -1,6 +1,13 @@ import { Component, computed, DestroyRef, inject, OnInit, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; +import { + ActivatedRoute, + NavigationEnd, + PRIMARY_OUTLET, + Router, + RouterOutlet, +} from '@angular/router'; +import { filter } from 'rxjs'; import { TenantService } from '../../services/tenant.service'; import { CartService } from '../../services/cart/cart.service'; import { StoreFooterComponent, StoreFooterSection } from './store-footer/store-footer.component'; @@ -37,6 +44,7 @@ export class StoreLayoutComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); protected readonly isCartOpen = signal(false); + protected readonly isCheckoutRoute = signal(this.isCheckoutUrl(this.router.url)); protected readonly isCreatingPurchase = signal(false); protected readonly displayCart = computed(() => this.tenant()?.display_cart ?? true); protected readonly cartEditingPolicy = computed(() => this.tenant()?.cart_editing_policy ?? null); @@ -149,8 +157,22 @@ export class StoreLayoutComponent implements OnInit { }); ngOnInit(): void { + this.router.events + .pipe( + filter((event): event is NavigationEnd => event instanceof NavigationEnd), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((event) => { + const isCheckoutRoute = this.isCheckoutUrl(event.urlAfterRedirects); + this.isCheckoutRoute.set(isCheckoutRoute); + + if (isCheckoutRoute) { + this.isCartOpen.set(false); + } + }); + this.route.queryParamMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => { - if (params.get('openCart') === 'true') { + if (params.get('openCart') === 'true' && !this.isCheckoutRoute()) { this.isCartOpen.set(true); } }); @@ -160,10 +182,24 @@ export class StoreLayoutComponent implements OnInit { }); } + private isCheckoutUrl(url: string): boolean { + const primarySegments = this.router.parseUrl(url).root.children[PRIMARY_OUTLET]?.segments ?? []; + + return primarySegments[0]?.path === 'checkout'; + } + protected onLoginClick(): void { void this.router.navigate(['/login']); } + protected onCartClick(): void { + if (this.isCheckoutRoute()) { + return; + } + + this.isCartOpen.update((isOpen) => !isOpen); + } + protected onSearch(term: string): void { void this.router.navigate(['/buscar'], { queryParams: { q: term, page: 1 }, diff --git a/src/app/shared/components/cart-icon/cart-icon.component.html b/src/app/shared/components/cart-icon/cart-icon.component.html index 7cf6e49..48cc7d4 100644 --- a/src/app/shared/components/cart-icon/cart-icon.component.html +++ b/src/app/shared/components/cart-icon/cart-icon.component.html @@ -1,7 +1,9 @@