feat(cart): refactor cart item structure and update related components for consistency

This commit is contained in:
2026-07-16 10:02:24 -03:00
parent e4195a8567
commit 8b785fd631
10 changed files with 246 additions and 195 deletions

View File

@@ -1,4 +1,14 @@
import { ChangeDetectionStrategy, Component, computed, effect, inject, OnInit, signal, untracked, 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';
@@ -14,7 +24,13 @@ import { StepComponent } from '../../../../shared/components/stepper/step.compon
import { StepperComponent } from '../../../../shared/components/stepper/stepper.component';
import { CheckoutDataStepComponent } from './checkout-data-step.component';
import { CheckoutPaymentStepComponent } from './checkout-payment-step.component';
import { CheckoutForm, PaymentMethod, PaymentMethodOption, TransferAccount, TransferField } from './checkout-page.models';
import {
CheckoutForm,
PaymentMethod,
PaymentMethodOption,
TransferAccount,
TransferField,
} from './checkout-page.models';
@Component({
selector: 'app-checkout-page',
@@ -24,11 +40,11 @@ import { CheckoutForm, PaymentMethod, PaymentMethodOption, TransferAccount, Tran
StepperComponent,
StepComponent,
CheckoutDataStepComponent,
CheckoutPaymentStepComponent
CheckoutPaymentStepComponent,
],
templateUrl: './checkout-page.component.html',
styleUrl: './checkout-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutPageComponent implements OnInit {
private readonly formBuilder = inject(FormBuilder);
@@ -44,7 +60,7 @@ export class CheckoutPageComponent implements OnInit {
nombre: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]],
dni: ['', [Validators.required]],
telefono: ['', [Validators.required]]
telefono: ['', [Validators.required]],
});
protected readonly cartSubtotal = computed(() => {
@@ -66,7 +82,7 @@ export class CheckoutPageComponent implements OnInit {
protected readonly paymentMethods: ReadonlyArray<PaymentMethodOption> = [
{ id: 'qr', label: 'QR' },
{ id: 'transfer', label: 'Transferencia' },
{ id: 'telepagos', label: 'TelePagos' }
{ id: 'telepagos', label: 'TelePagos' },
];
protected readonly selectedPaymentMethod = signal<PaymentMethod>('qr');
protected readonly copiedTransferField = signal<TransferField | null>(null);
@@ -74,13 +90,15 @@ export class CheckoutPageComponent implements OnInit {
titular: 'Nombre y Apellido',
entidad: 'TelePagos',
cvu: '0000000000000000000000',
alias: 'telepagos.ar'
alias: 'telepagos.ar',
});
protected readonly isCreatingPurchase = signal(false);
protected readonly createdPurchaseId = signal<number | null>(null);
protected readonly isGeneratingIntent = signal(false);
protected readonly isPaymentLoading = computed(() => this.isGeneratingIntent() || this.cartService.isUpdating());
protected readonly isPaymentLoading = computed(
() => this.isGeneratingIntent() || this.cartService.isUpdating(),
);
protected readonly qrData = signal<string | null>(null);
constructor() {
@@ -88,7 +106,7 @@ export class CheckoutPageComponent implements OnInit {
// 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) {
@@ -108,7 +126,7 @@ export class CheckoutPageComponent implements OnInit {
nombre: user.nombre_apellido,
email: user.email,
dni: user.dni ?? '',
telefono: user.telefono ?? ''
telefono: user.telefono ?? '',
});
this.form.controls.nombre.disable();
this.form.controls.email.disable();
@@ -138,14 +156,14 @@ export class CheckoutPageComponent implements OnInit {
}
return {
productVariantId: item.product_variant_id,
cartItemId: item.id,
imageUrl: item.product?.imagen ?? null,
product,
originalPrice: null,
discountedPrice: parseFloat(item.precio_unitario),
discountPercentage: null,
attributes,
quantity: item.cantidad
quantity: item.cantidad,
};
}
@@ -174,12 +192,11 @@ export class CheckoutPageComponent implements OnInit {
const response = await this.checkoutService.createPurchase(tenant.codigo, payload);
this.createdPurchaseId.set(response.id);
this.stepper.next();
// Auto trigger intent for default option
void this.selectPaymentMethod(this.selectedPaymentMethod());
} catch (error) {
console.error('Failed to create purchase:', error);
// Here we could show an alert or toast
@@ -205,8 +222,12 @@ export class CheckoutPageComponent implements OnInit {
this.isGeneratingIntent.set(true);
try {
const response = await this.checkoutService.generatePaymentIntent(tenant.codigo, purchaseId, method);
const response = await this.checkoutService.generatePaymentIntent(
tenant.codigo,
purchaseId,
method,
);
if (method === 'qr' && response.qr_data?.qr_code) {
this.qrData.set(response.qr_data.qr_code);
} else if (method === 'transfer' && response.transfer_data) {
@@ -214,7 +235,7 @@ export class CheckoutPageComponent implements OnInit {
titular: response.transfer_data.titular,
entidad: response.transfer_data.entidad,
cvu: response.transfer_data.cvu,
alias: response.transfer_data.alias
alias: response.transfer_data.alias,
});
}
} catch (error) {

View File

@@ -427,7 +427,7 @@ describe('ProductDetailPageComponent', () => {
addToCartButton.click();
fixture.detectChanges();
expect(cartServiceStub.addItem).toHaveBeenCalledWith(123, 3);
expect(cartServiceStub.addItem).toHaveBeenCalledWith('variant', 123, 3);
expect(toastServiceStub.success).toHaveBeenCalledWith('Producto agregado al carrito');
});

View File

@@ -229,7 +229,7 @@ export class ProductDetailPageComponent implements OnInit, OnDestroy {
}
this.addingToCart.set(true);
this.cartService.addItem(variant.variant_id, this.quantity()).subscribe({
this.cartService.addItem('variant', variant.variant_id, this.quantity()).subscribe({
next: (res) => {
const msg = res.message || 'Producto agregado al carrito';
this.toastService.success(msg);