fix(checkout): disable cart access

This commit is contained in:
2026-08-21 15:50:12 -03:00
parent c24a8b946d
commit 195ac73aed
9 changed files with 111 additions and 15 deletions

View File

@@ -79,10 +79,13 @@
@if (displayCart()) {
<app-cart-icon
[quantity]="cartQuantity()"
ariaLabel="Carrito de compras"
title="Carrito"
(click)="cartClick.emit()"
[quantity]="cartDisabled() ? null : cartQuantity()"
[disabled]="cartDisabled()"
[ariaLabel]="
cartDisabled() ? 'Carrito no disponible durante el checkout' : 'Carrito de compras'
"
[title]="cartDisabled() ? 'Carrito no disponible durante el checkout' : 'Carrito'"
(click)="onCartClick()"
/>
}

View File

@@ -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<void>();
readonly ticketsClick = output<void>();
readonly loginClick = output<void>();
@@ -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()) {

View File

@@ -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()) {
<div class="store-layout__cart-overlay" (click)="isCartOpen.set(false)"></div>
<div class="store-layout__cart-dropdown card border-0 shadow-lg">
<app-cart

View File

@@ -8,6 +8,7 @@ import {
ParamMap,
provideRouter,
Router,
UrlSerializer,
} from '@angular/router';
import { BehaviorSubject, of } from 'rxjs';
@@ -22,6 +23,7 @@ import { AuthUser } from '../../services/auth/auth.interfaces';
import { StoreLayoutComponent } from './store-layout.component';
import { StoreHeaderComponent } from './store-header/store-header.component';
import { CheckoutService } from '../../services/checkout.service';
import { TenantUrlSerializer } from '../../services/tenant-url.serializer';
const tenant: Tenant = {
id: 1,
@@ -164,6 +166,7 @@ describe('StoreLayoutComponent', () => {
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<HTMLButtonElement>('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,

View File

@@ -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 },