From cd1c99f487b105725833c73501b8fb3183e714ba Mon Sep 17 00:00:00 2001 From: ncoronel Date: Mon, 21 Sep 2026 10:59:51 -0300 Subject: [PATCH] feat(checkout): show event purchase summary --- .../store-layout/store-layout.component.html | 2 +- .../store-layout.component.spec.ts | 20 +++++++++ .../store-layout/store-layout.component.ts | 5 +++ src/app/core/services/checkout.service.ts | 9 +++- src/app/core/services/tenant.interface.ts | 1 + .../purchase-detail-page.ts | 2 +- .../checkout-page.component.html | 21 ++++++++- .../checkout-page.component.scss | 32 +++++++++++++ .../checkout-page.component.spec.ts | 45 +++++++++++++++++++ .../checkout-page/checkout-page.component.ts | 8 +++- 10 files changed, 140 insertions(+), 5 deletions(-) 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 f7e7bdf..e559ac9 100644 --- a/src/app/core/layout/store-layout/store-layout.component.html +++ b/src/app/core/layout/store-layout/store-layout.component.html @@ -17,7 +17,7 @@ [displaySeachBar]="tenant()?.display_seach_bar ?? true" [displayCart]="displayCart()" [cartDisabled]="isCheckoutRoute()" - [checkoutRemainingSeconds]="checkoutRemainingSeconds()" + [checkoutRemainingSeconds]="headerCheckoutRemainingSeconds()" (cartClick)="onCartClick()" (ticketsClick)="onTicketsClick()" (loginClick)="onLoginClick()" diff --git a/src/app/core/layout/store-layout/store-layout.component.spec.ts b/src/app/core/layout/store-layout/store-layout.component.spec.ts index 38257db..615a597 100644 --- a/src/app/core/layout/store-layout/store-layout.component.spec.ts +++ b/src/app/core/layout/store-layout/store-layout.component.spec.ts @@ -428,6 +428,26 @@ describe('StoreLayoutComponent', () => { ); }); + it('moves the countdown out of the header on multi-event checkout', () => { + tenantState.set({ + ...tenant, + storefront_website_type_code: 'onticket_multi_event', + checkout_summary_type: 'event', + }); + checkoutRemainingSecondsState.set(587); + const router = TestBed.inject(Router); + vi.spyOn(router, 'url', 'get').mockReturnValue('/checkout/25'); + + const fixture = TestBed.createComponent(StoreLayoutComponent); + fixture.detectChanges(); + + const element = fixture.nativeElement as HTMLElement; + const header = fixture.debugElement.query(By.directive(StoreHeaderComponent)); + + expect(header.componentInstance.checkoutRemainingSeconds()).toBeNull(); + expect(element.querySelector('.store-layout__checkout-timer')).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 32af3f7..7ebd321 100644 --- a/src/app/core/layout/store-layout/store-layout.component.ts +++ b/src/app/core/layout/store-layout/store-layout.component.ts @@ -74,6 +74,11 @@ export class StoreLayoutComponent implements OnInit { private readonly isMultiEventStorefront = computed( () => this.tenant()?.storefront_website_type_code === 'onticket_multi_event', ); + protected readonly headerCheckoutRemainingSeconds = computed(() => + this.isCheckoutRoute() && this.tenant()?.checkout_summary_type === 'event' + ? null + : this.checkoutRemainingSeconds(), + ); protected readonly navigationCategories = computed(() => this.isMultiEventStorefront() ? (this.tenant()?.event_categories ?? []) diff --git a/src/app/core/services/checkout.service.ts b/src/app/core/services/checkout.service.ts index 4cad0da..624d238 100644 --- a/src/app/core/services/checkout.service.ts +++ b/src/app/core/services/checkout.service.ts @@ -121,11 +121,17 @@ export interface PurchaseDetailItemResponse { nombre: string; descripcion: string | null; slug: string; - imagen: string | null; + imagen?: string | null; attributes: PurchaseDetailAttributeResponse[]; }; } +export interface PurchaseCheckoutEventResponse { + id: number; + title: string; + image: string | null; +} + export interface PurchaseDetailResponse extends PurchaseStatusResponse { id: number; cart_id: number | null; @@ -140,6 +146,7 @@ export interface PurchaseDetailResponse extends PurchaseStatusResponse { email: string | null; items_source: 'purchase'; items: PurchaseDetailItemResponse[]; + event?: PurchaseCheckoutEventResponse | null; tickets_count?: number; has_generated_tickets?: boolean; subtotal: string; diff --git a/src/app/core/services/tenant.interface.ts b/src/app/core/services/tenant.interface.ts index b846a6a..c7866a1 100644 --- a/src/app/core/services/tenant.interface.ts +++ b/src/app/core/services/tenant.interface.ts @@ -134,6 +134,7 @@ export interface Tenant { footer_bg_image?: string | null; storefront_website_type_code?: string | null; header_type?: 'standard' | 'immersive'; + checkout_summary_type?: 'default' | 'event'; extras?: WebsiteExtras; event?: TenantEvent | null; selected_bank_account_id?: number | null; diff --git a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts index ee0c7a0..871b6ae 100644 --- a/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts +++ b/src/app/features/store/pages/account-page/pages/purchase-detail-page/purchase-detail-page.ts @@ -103,7 +103,7 @@ export class PurchaseDetailPage implements OnInit { id: item.id, title: item.item_details.nombre || 'Producto sin nombre', description: item.item_details.descripcion, - imageUrl: item.item_details.imagen, + imageUrl: item.item_details.imagen ?? null, quantity: item.quantity, attributes: item.item_details.attributes.map((attribute) => ({ label: attribute.name, 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 27c6d57..7207835 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 @@ -47,7 +47,26 @@
- @if (checkoutRemainingTime(); as remainingTime) { + @if (usesEventCheckoutSummary()) { + @if (checkoutRemainingTime(); as remainingTime) { +
+ Tiempo restante de compra: + {{ remainingTime }} +
+ } + + @if (checkoutEvent(); as event) { + @if (event.image) { +
+ +
+ } + } + } @else if (checkoutRemainingTime(); as remainingTime) {
{ let tenantState: ReturnType< typeof signal<{ codigo: string; + storefront_website_type_code?: string | null; + checkout_summary_type?: 'default' | 'event'; checkout_editing_policy?: CartEditingPolicy; }> >; @@ -468,6 +470,49 @@ describe('CheckoutPageComponent payment validation', () => { }); }); + it('uses the event image and removes item images for multi-event checkout', () => { + tenantState.set({ + ...tenantState(), + checkout_summary_type: 'event', + }); + const { component } = createComponent(); + component.createdPurchase.set({ + id: 25, + status: 'created', + event: { + id: 12, + title: 'Festival de prueba', + image: 'https://example.com/event.jpg', + }, + items: [ + { + id: 91, + quantity: 1, + unit_price: '10000.00', + line_total: '10000.00', + source_catalog_item_id: 8, + source_variant_id: null, + item_details: { + nombre: 'Entrada general', + descripcion: null, + slug: 'entrada-general', + imagen: 'https://example.com/product.jpg', + attributes: [], + }, + }, + ], + subtotal: '10000.00', + total: '10000.00', + }); + + expect(component.checkoutEvent()).toEqual({ + id: 12, + title: 'Festival de prueba', + image: 'https://example.com/event.jpg', + }); + expect(component.mappedCartItems()[0].imageUrl).toBeNull(); + }); + it('keeps the checkout hidden while the purchase is loading', async () => { let resolvePurchase!: (purchase: any) => void; routeParamMap = convertToParamMap({ id: 25 }); 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 25201a7..909324e 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 @@ -138,6 +138,12 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { const purchase = this.createdPurchase(); return purchase ? purchase.items.map((item) => this.mapPurchaseItemToMock(item)) : []; }); + protected readonly usesEventCheckoutSummary = computed( + () => this.tenantService.tenant()?.checkout_summary_type === 'event', + ); + protected readonly checkoutEvent = computed(() => + this.usesEventCheckoutSummary() ? (this.createdPurchase()?.event ?? null) : null, + ); protected readonly isStep1Valid = signal(this.form.valid); protected readonly paymentMethods: ReadonlyArray = [ @@ -235,7 +241,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy { private mapPurchaseItemToMock(item: PurchaseDetailItemResponse): CartItemMock { return { cartItemId: item.id, - imageUrl: item.item_details.imagen, + imageUrl: this.usesEventCheckoutSummary() ? null : (item.item_details.imagen ?? null), product: item.item_details.nombre, originalPrice: null, discountedPrice: parseFloat(item.unit_price),