From 53278719a8f48fa4113589a6f75c5b7d102270d5 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Mon, 6 Jul 2026 15:28:39 -0300 Subject: [PATCH] feat(cart): add loading state management for cart item operations and update payment intent handling --- src/app/core/services/cart/cart.service.ts | 35 ++++++++++++++++--- .../checkout-page.component.html | 2 +- .../checkout-page/checkout-page.component.ts | 17 ++++++++- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/app/core/services/cart/cart.service.ts b/src/app/core/services/cart/cart.service.ts index 6711d95..07f0393 100644 --- a/src/app/core/services/cart/cart.service.ts +++ b/src/app/core/services/cart/cart.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable, signal } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { map, Observable, tap } from 'rxjs'; +import { catchError, map, Observable, tap } from 'rxjs'; import { ApiResponse } from '../api-response.interface'; import { TenantService } from '../tenant.service'; @@ -16,6 +16,9 @@ export class CartService { private readonly cartState = signal(null); readonly cart = this.cartState.asReadonly(); + private readonly isUpdatingState = signal(false); + readonly isUpdating = this.isUpdatingState.asReadonly(); + private get tenantApiUrl(): string { return this.tenantService.getTenantApiUrl(); } @@ -32,6 +35,7 @@ export class CartService { } addItem(productVariantId: number, cantidad: number): Observable> { + this.isUpdatingState.set(true); return this.http .post>( `${this.tenantApiUrl}/cart/items`, @@ -39,11 +43,19 @@ export class CartService { { withCredentials: true } ) .pipe( - tap((response) => this.cartState.set(response.data)) + tap((response) => { + this.cartState.set(response.data); + this.isUpdatingState.set(false); + }), + catchError((error) => { + this.isUpdatingState.set(false); + throw error; + }) ); } updateItemQuantity(productVariantId: number, cantidad: number): Observable> { + this.isUpdatingState.set(true); return this.http .patch>( `${this.tenantApiUrl}/cart/items/${productVariantId}`, @@ -51,18 +63,33 @@ export class CartService { { withCredentials: true } ) .pipe( - tap((response) => this.cartState.set(response.data)) + tap((response) => { + this.cartState.set(response.data); + this.isUpdatingState.set(false); + }), + catchError((error) => { + this.isUpdatingState.set(false); + throw error; + }) ); } removeItem(productVariantId: number): Observable> { + this.isUpdatingState.set(true); return this.http .delete>( `${this.tenantApiUrl}/cart/items/${productVariantId}`, { withCredentials: true } ) .pipe( - tap((response) => this.cartState.set(response.data)) + tap((response) => { + this.cartState.set(response.data); + this.isUpdatingState.set(false); + }), + catchError((error) => { + this.isUpdatingState.set(false); + throw error; + }) ); } } diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.html b/src/app/features/store/pages/checkout-page/checkout-page.component.html index da2d08c..a499ad7 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.html +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.html @@ -16,7 +16,7 @@ [selectedPaymentMethod]="selectedPaymentMethod()" [copiedTransferField]="copiedTransferField()" [transferAccount]="transferAccount()" - [isGeneratingIntent]="isGeneratingIntent()" + [isGeneratingIntent]="isPaymentLoading()" [qrData]="qrData()" (paymentMethodChange)="selectPaymentMethod($event)" (copyTransferValue)="copyTransferValue($event.field, $event.value)" diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.ts index 197b53e..241392e 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.ts +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, OnInit, signal, untracked, ViewChild } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { startWith } from 'rxjs'; @@ -80,9 +80,24 @@ export class CheckoutPageComponent implements OnInit { protected readonly isCreatingPurchase = signal(false); protected readonly createdPurchaseId = signal(null); protected readonly isGeneratingIntent = signal(false); + protected readonly isPaymentLoading = computed(() => this.isGeneratingIntent() || this.cartService.isUpdating()); protected readonly qrData = signal(null); constructor() { + effect(() => { + // We only want to trigger the intent generation when the cart changes. + // So we track the cart, but untrack the other signals to prevent duplicate calls. + const cart = this.cartService.cart(); + + untracked(() => { + const purchaseId = this.createdPurchaseId(); + if (cart && purchaseId) { + // Trigger payment intent generation when cart changes and we are on the payment step + void this.selectPaymentMethod(this.selectedPaymentMethod()); + } + }); + }); + this.form.statusChanges .pipe(startWith(this.form.status)) .subscribe(() => this.isStep1Valid.set(this.form.valid));